机器管理
This commit is contained in:
parent
0f8f6f837d
commit
6e317f9b8c
3
.idea/compiler.xml
generated
3
.idea/compiler.xml
generated
@ -7,6 +7,7 @@
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="machine-management-module" />
|
||||
</profile>
|
||||
<profile name="Annotation profile for ops-pro" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
@ -53,6 +54,7 @@
|
||||
<module name="module-ci-dispatch-api" target="17" />
|
||||
<module name="module-ci-environment" target="17" />
|
||||
<module name="module-ci-event" target="17" />
|
||||
<module name="module-ci-execute" target="1.5" />
|
||||
<module name="module-ci-log" target="17" />
|
||||
<module name="module-ci-market" target="17" />
|
||||
<module name="module-ci-project" target="17" />
|
||||
@ -69,6 +71,7 @@
|
||||
<module name="app-plugins" options="-parameters" />
|
||||
<module name="commons" options="-parameters" />
|
||||
<module name="framework" options="-parameters" />
|
||||
<module name="machine-management-module" options="-parameters" />
|
||||
<module name="module-ci-commons" options="-parameters" />
|
||||
<module name="module-ci-engine" options="-parameters" />
|
||||
<module name="module-ci-plugin" options="-parameters" />
|
||||
|
1
.idea/encodings.xml
generated
1
.idea/encodings.xml
generated
@ -39,6 +39,7 @@
|
||||
<file url="file://$PROJECT_DIR$/framework/spring-boot-starter-websocket/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/framework/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/framework/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/ee/machine-management-module/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common-pipeline/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common-pipeline/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common/src/main/java" charset="UTF-8" />
|
||||
|
10
.idea/jarRepositories.xml
generated
10
.idea/jarRepositories.xml
generated
@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="huaweicloud" />
|
||||
<option name="name" value="huawei" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="huaweicloud" />
|
||||
<option name="name" value="huawei" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="aliyunmaven" />
|
||||
<option name="name" value="aliyun" />
|
||||
|
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -52,6 +52,7 @@
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-store-api/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-process-biz/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-process-api/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/ee/machine-management-module/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="ignoredFiles">
|
||||
|
2
modules/ee/machine-management-module/.gitattributes
vendored
Normal file
2
modules/ee/machine-management-module/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/mvnw text eol=lf
|
||||
*.cmd text eol=crlf
|
33
modules/ee/machine-management-module/.gitignore
vendored
Normal file
33
modules/ee/machine-management-module/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
259
modules/ee/machine-management-module/mvnw
vendored
Normal file
259
modules/ee/machine-management-module/mvnw
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
set -euf
|
||||
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||
|
||||
# OS specific support.
|
||||
native_path() { printf %s\\n "$1"; }
|
||||
case "$(uname)" in
|
||||
CYGWIN* | MINGW*)
|
||||
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||
native_path() { cygpath --path --windows "$1"; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# set JAVACMD and JAVACCMD
|
||||
set_java_home() {
|
||||
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||
if [ -n "${JAVA_HOME-}" ]; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||
|
||||
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v java
|
||||
)" || :
|
||||
JAVACCMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v javac
|
||||
)" || :
|
||||
|
||||
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# hash string like Java String::hashCode
|
||||
hash_string() {
|
||||
str="${1:-}" h=0
|
||||
while [ -n "$str" ]; do
|
||||
char="${str%"${str#?}"}"
|
||||
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||
str="${str#?}"
|
||||
done
|
||||
printf %x\\n $h
|
||||
}
|
||||
|
||||
verbose() { :; }
|
||||
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||
|
||||
die() {
|
||||
printf %s\\n "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||
esac
|
||||
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
|
||||
case "${distributionUrl##*/}" in
|
||||
maven-mvnd-*bin.*)
|
||||
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||
*)
|
||||
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||
distributionPlatform=linux-amd64
|
||||
;;
|
||||
esac
|
||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||
;;
|
||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||
esac
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||
|
||||
exec_maven() {
|
||||
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||
}
|
||||
|
||||
if [ -d "$MAVEN_HOME" ]; then
|
||||
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
exec_maven "$@"
|
||||
fi
|
||||
|
||||
case "${distributionUrl-}" in
|
||||
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||
esac
|
||||
|
||||
# prepare tmp dir
|
||||
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||
trap clean HUP INT TERM EXIT
|
||||
else
|
||||
die "cannot create temp dir"
|
||||
fi
|
||||
|
||||
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||
|
||||
# Download and Install Apache Maven
|
||||
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
verbose "Downloading from: $distributionUrl"
|
||||
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
# select .zip or .tar.gz
|
||||
if ! command -v unzip >/dev/null; then
|
||||
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
fi
|
||||
|
||||
# verbose opt
|
||||
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||
|
||||
# normalize http auth
|
||||
case "${MVNW_PASSWORD:+has-password}" in
|
||||
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
esac
|
||||
|
||||
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||
verbose "Found wget ... using wget"
|
||||
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||
verbose "Found curl ... using curl"
|
||||
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||
elif set_java_home; then
|
||||
verbose "Falling back to use Java to download"
|
||||
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
cat >"$javaSource" <<-END
|
||||
public class Downloader extends java.net.Authenticator
|
||||
{
|
||||
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||
}
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
setDefault( new Downloader() );
|
||||
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||
}
|
||||
}
|
||||
END
|
||||
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||
verbose " - Compiling Downloader.java ..."
|
||||
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||
verbose " - Running Downloader.java ..."
|
||||
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||
fi
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
if [ -n "${distributionSha256Sum-}" ]; then
|
||||
distributionSha256Result=false
|
||||
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
elif command -v sha256sum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
elif command -v shasum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ $distributionSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# unzip and move
|
||||
if command -v unzip >/dev/null; then
|
||||
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||
else
|
||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||
fi
|
||||
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
|
||||
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||
|
||||
clean || :
|
||||
exec_maven "$@"
|
149
modules/ee/machine-management-module/mvnw.cmd
vendored
Normal file
149
modules/ee/machine-management-module/mvnw.cmd
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
<# : batch portion
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.3.2
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||
@SET __MVNW_CMD__=
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@SET __MVNW_PSMODULEP_SAVE=
|
||||
@SET __MVNW_ARG0_NAME__=
|
||||
@SET MVNW_USERNAME=
|
||||
@SET MVNW_PASSWORD=
|
||||
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
|
||||
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||
@GOTO :EOF
|
||||
: end batch / begin powershell #>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($env:MVNW_VERBOSE -eq "true") {
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
|
||||
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||
"maven-mvnd-*" {
|
||||
$USE_MVND = $true
|
||||
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||
$MVN_CMD = "mvnd.cmd"
|
||||
break
|
||||
}
|
||||
default {
|
||||
$USE_MVND = $false
|
||||
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
if ($env:MVNW_REPOURL) {
|
||||
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
|
||||
}
|
||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
|
||||
}
|
||||
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||
|
||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
exit $?
|
||||
}
|
||||
|
||||
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||
}
|
||||
|
||||
# prepare tmp dir
|
||||
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||
trap {
|
||||
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||
|
||||
# Download and Install Apache Maven
|
||||
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
Write-Verbose "Downloading from: $distributionUrl"
|
||||
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
}
|
||||
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||
}
|
||||
}
|
||||
|
||||
# unzip and move
|
||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||
try {
|
||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||
} catch {
|
||||
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||
Write-Error "fail to move MAVEN_HOME"
|
||||
}
|
||||
} finally {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
101
modules/ee/machine-management-module/pom.xml
Normal file
101
modules/ee/machine-management-module/pom.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.13-SNAPSHOT</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>machine-management-module</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>machine</name>
|
||||
<description>machine</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>3.15.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 机器连接-->
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.32</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- SpringDoc OpenAPI 依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,39 @@
|
||||
package com.casic.machine.configuration;
|
||||
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
|
||||
import com.casic.machine.exception.ServiceException;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Getter
|
||||
public class AliYunConfig {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AliYunConfig.class);
|
||||
@Value("${aliyun.oss.endpoint}")
|
||||
private String endpoint;
|
||||
@Value("${aliyun.oss.accessKeyId}")
|
||||
private String accessKeyId;
|
||||
@Value("${aliyun.oss.accessKeySecret}")
|
||||
private String accessKeySecret;
|
||||
@Value("${aliyun.oss.bucketName}")
|
||||
private String bucketName;
|
||||
|
||||
@Bean
|
||||
public OSS ossClient() {
|
||||
logger.info("OSS域名: {}", endpoint);
|
||||
logger.info("AccessKeyId: {}", accessKeyId);
|
||||
|
||||
// 添加参数校验
|
||||
if (endpoint == null || accessKeyId == null || accessKeySecret == null) {
|
||||
throw new ServiceException(ServiceException.OSS_PARAM_NULL,"OSS参数为空");
|
||||
}
|
||||
|
||||
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.casic.machine.contants;
|
||||
|
||||
public interface CommonConstants {
|
||||
String DEFAULT_PACKAGE_NAME = "com.casic";
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.casic.machine.controller;
|
||||
|
||||
|
||||
import com.casic.machine.dto.MachineEnvDTO;
|
||||
import com.casic.machine.pojo.ResponseData;
|
||||
import com.casic.machine.service.MachineEnvService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 环境变量控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/machineEnv")
|
||||
@Tag(name = "环境变量管理")
|
||||
@RequiredArgsConstructor
|
||||
public class MachineEnvController {
|
||||
|
||||
@Resource
|
||||
private MachineEnvService machineEnvService;
|
||||
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "新增环境变量")
|
||||
public ResponseData add(@RequestBody MachineEnvDTO machineEnvDTO) {
|
||||
return machineEnvService.add(machineEnvDTO) ? ResponseData.success() : ResponseData.error("新增机器的环境变量失败");
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改环境变量")
|
||||
public ResponseData update(@RequestBody MachineEnvDTO machineEnvDTO) {
|
||||
return machineEnvService.update(machineEnvDTO) ? ResponseData.success() : ResponseData.error("修改环境变量失败");
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除机器的环境变量")
|
||||
public ResponseData deleteByMachineId(
|
||||
@RequestParam Long machineId) {
|
||||
machineEnvService.deleteByMachineId(machineId);
|
||||
return ResponseData.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary = "批量删除机器环境变量")
|
||||
public ResponseData deleteList(@RequestParam List<Long> ids){
|
||||
machineEnvService.deleteList(ids);
|
||||
return ResponseData.success();
|
||||
}
|
||||
|
||||
@GetMapping("/listByMachineId")
|
||||
@Operation(summary = "获取机器的环境变量")
|
||||
public ResponseData getByMachineId(
|
||||
@RequestParam Long machineId) {
|
||||
return ResponseData.success(machineEnvService.getByMachineId(machineId));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary ="获取环境变量列表")
|
||||
public ResponseData list(@RequestBody MachineEnvDTO machineEnvDTO) {
|
||||
return ResponseData.success(machineEnvService.listEnv(machineEnvDTO));
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package com.casic.machine.controller;
|
||||
|
||||
|
||||
import com.casic.machine.dto.MachineInfoDto;
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
|
||||
import com.casic.machine.pojo.ResponseData;
|
||||
import com.casic.machine.pojo.SuccessResponseData;
|
||||
import com.casic.machine.service.MachineInfoService;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "机器信息管理")
|
||||
@RequestMapping("/api/machineInfo")
|
||||
public class MachineInfoController {
|
||||
@Resource
|
||||
private MachineInfoService machineInfoService;
|
||||
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "新增机器信息")
|
||||
public ResponseData add(@RequestBody MachineInfoDto machineInfoDto) {
|
||||
return machineInfoService.addMachineInfo(machineInfoDto) ? ResponseData.success() : ResponseData.error("新增失败");
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取机器信息列表")
|
||||
public ResponseData list(@RequestBody MachineInfoDto machineInfoDto) {
|
||||
return SuccessResponseData.success(machineInfoService.listMachineInfo(machineInfoDto));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "编辑机器信息")
|
||||
public ResponseData update(@RequestBody MachineInfoDto machineInfoDto) {
|
||||
return machineInfoService.updateMachineInfo(machineInfoDto) ? ResponseData.success() : ResponseData.error("更新失败");
|
||||
}
|
||||
|
||||
@PutMapping("/updateStatus")
|
||||
@Operation(summary = "机器启用/停用")
|
||||
public ResponseData updateStatus(@RequestParam("machineInfoId") Long machineInfoId, @RequestParam("status") String status) {
|
||||
return machineInfoService.updateStatus(machineInfoId, status) ? ResponseData.success() : ResponseData.error("更新状态失败");
|
||||
}
|
||||
|
||||
@PutMapping("/bindingSecretKey")
|
||||
@Operation(summary = "绑定密钥")
|
||||
public ResponseData bindingSecretKey(@RequestParam("machineInfoId") Long machineInfoId, @RequestParam("secretKeyId") Long secretKeyId) {
|
||||
return machineInfoService.bindingSecretKey(machineInfoId, secretKeyId) ? ResponseData.success() : ResponseData.error("绑定失败");
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "机器信息删除")
|
||||
public ResponseData delete(@RequestParam("machineInfoId") Long machineInfoId) {
|
||||
machineInfoService.deleteMachineInfo(machineInfoId);
|
||||
return ResponseData.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary = "批量删除机器信息")
|
||||
public ResponseData deleteList(@RequestParam("machineInfoId") List<Long> machineInfoIds) {
|
||||
machineInfoService.deleteList(machineInfoIds);
|
||||
return ResponseData.success();
|
||||
}
|
||||
|
||||
@PostMapping("/test")
|
||||
@Operation(summary = "测试机器连接")
|
||||
public ResponseData testConnection(@RequestBody MachineInfo machineInfo) {
|
||||
boolean result = machineInfoService.testConnection(machineInfo);
|
||||
return ResponseData.success();
|
||||
}
|
||||
|
||||
@GetMapping("/status/{machineName}")
|
||||
@Operation(summary = "获取机器连接状态")
|
||||
public ResponseData getConnectionStatus(@PathVariable String machineName) {
|
||||
return ResponseData.success(machineInfoService.getConnectionStatus(machineName));
|
||||
}
|
||||
|
||||
@GetMapping("/status/all")
|
||||
@Operation(summary = "获取所有机器连接状态")
|
||||
public ResponseData getAllConnectionStatus() {
|
||||
return ResponseData.success(machineInfoService.getAllConnectionStatus());
|
||||
}
|
||||
|
||||
@PostMapping("/connect")
|
||||
@Operation(summary = "建立机器连接")
|
||||
public ResponseData connect(@RequestBody MachineInfo machineInfo) {
|
||||
return ResponseData.success(machineInfoService.connect(machineInfo));
|
||||
}
|
||||
|
||||
@PostMapping("/disconnect/{sessionId}")
|
||||
@Operation(summary = "断开机器连接")
|
||||
public ResponseData disconnect(@PathVariable String sessionId) {
|
||||
return ResponseData.success(machineInfoService.disconnect(sessionId));
|
||||
}
|
||||
|
||||
@PostMapping("/execute/{sessionId}")
|
||||
@Operation(summary = "执行远程命令")
|
||||
public ResponseData executeCommand(
|
||||
@PathVariable String sessionId,
|
||||
@RequestBody String command) {
|
||||
return ResponseData.success(machineInfoService.executeCommand(sessionId, command));
|
||||
}
|
||||
|
||||
@PostMapping("/upload/{sessionId}")
|
||||
@Operation(summary = "上传文件到远程机器")
|
||||
public ResponseData uploadFile(
|
||||
@PathVariable String sessionId,
|
||||
@RequestParam String localFilePath,
|
||||
@RequestParam String remoteFilePath) {
|
||||
return ResponseData.success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath));
|
||||
}
|
||||
|
||||
@PostMapping("/download/{sessionId}")
|
||||
@Operation(summary = "从远程机器下载文件")
|
||||
public ResponseData downloadFile(
|
||||
@PathVariable String sessionId,
|
||||
@RequestParam String remoteFilePath,
|
||||
@RequestParam String localFilePath) {
|
||||
return ResponseData.success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath));
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package com.casic.machine.controller;
|
||||
|
||||
import com.casic.machine.dto.MachineProxyDTO;
|
||||
import com.casic.machine.pojo.ResponseData;
|
||||
import com.casic.machine.pojo.SuccessResponseData;
|
||||
import com.casic.machine.service.MachineProxyService;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 机器代理控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/machineProxy")
|
||||
@Tag(name = "机器代理管理")
|
||||
@RequiredArgsConstructor
|
||||
public class MachineProxyController {
|
||||
|
||||
@Resource
|
||||
private MachineProxyService machineProxyService;
|
||||
|
||||
@PostMapping("/register")
|
||||
@Operation(summary ="注册新的机器代理")
|
||||
public ResponseData register(@RequestBody MachineProxyDTO machineProxyDTO) {
|
||||
return machineProxyService.register(machineProxyDTO) ? ResponseData.success() :ResponseData.error("注册新的机器代理失败");
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary ="获取代理")
|
||||
public ResponseData list(MachineProxyDTO machineProxyDTO) {
|
||||
return ResponseData.success(machineProxyService.list(machineProxyDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/updateStatus")
|
||||
@Operation(summary ="更新代理状态")
|
||||
public ResponseData updateStatus(@RequestBody MachineProxyDTO machineProxyDTO) {
|
||||
return machineProxyService.updateStatus(machineProxyDTO) ? ResponseData.success() : ResponseData.error("更新代理状态失败");
|
||||
}
|
||||
|
||||
@PostMapping("/receiptHeartbeat")
|
||||
@Operation(summary ="接收代理心跳")
|
||||
public ResponseData heartbeat(@RequestBody MachineProxyDTO machineProxyDTO) {
|
||||
return machineProxyService.heartbeat(machineProxyDTO) ? ResponseData.success() :ResponseData.error("接收代理心跳失败");
|
||||
}
|
||||
|
||||
@GetMapping("/statistics/status")
|
||||
@Operation(summary ="获取所有代理的状态统计")
|
||||
public ResponseData getStatusStatistics() {
|
||||
return ResponseData.success(machineProxyService.getStatusStatistics());
|
||||
}
|
||||
|
||||
@PutMapping("/updateConfig")
|
||||
@Operation(summary ="更新代理配置")
|
||||
public ResponseData updateConfig(@RequestBody MachineProxyDTO machineProxyDTO) {
|
||||
return machineProxyService.updateConfig(machineProxyDTO) ? ResponseData.success() : ResponseData.error("更新代理配置失败");
|
||||
}
|
||||
|
||||
@DeleteMapping("/batch")
|
||||
@Operation(summary ="批量删除代理")
|
||||
public ResponseData deleteBatch(@RequestBody List<Long> ids) {
|
||||
machineProxyService.delete(ids);
|
||||
return new SuccessResponseData();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.casic.machine.controller;
|
||||
|
||||
|
||||
import com.casic.machine.dto.SecretKeyDto;
|
||||
import com.casic.machine.pojo.ResponseData;
|
||||
import com.casic.machine.service.SecretKeyService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/secretKey")
|
||||
@Tag(name = "密钥管理")
|
||||
public class SecretKeyController {
|
||||
@Resource
|
||||
private SecretKeyService secretKeyService;
|
||||
|
||||
@PostMapping(value = "/add", consumes = "multipart/form-data")
|
||||
@Operation(summary ="新增密钥")
|
||||
public ResponseData add(@RequestPart("secretKeyDto") SecretKeyDto secretKeyDto, @RequestPart("file") MultipartFile file) {
|
||||
return secretKeyService.addSecretKey(secretKeyDto, file) ? ResponseData.success() : ResponseData.error("新增密钥失败");
|
||||
}
|
||||
|
||||
@PutMapping("/bindingMachine")
|
||||
@Operation(summary ="绑定机器")
|
||||
public ResponseData bindingMachine(@RequestParam("secretKeyId") Long secretKeyId, @RequestParam("machineInfoIds") List<Long> machineInfoIds) {
|
||||
secretKeyService.bindingMachine(secretKeyId, machineInfoIds);
|
||||
return ResponseData.success();
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary ="编辑密钥信息")
|
||||
public ResponseData update(@RequestPart("secretKeyDto") SecretKeyDto secretKeyDto, @RequestPart(value = "file", required = false) MultipartFile file) {
|
||||
return secretKeyService.updateSecretKey(secretKeyDto, file) ? ResponseData.success() : ResponseData.error("编辑密钥信息失败");
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary ="删除密钥")
|
||||
public ResponseData delete(@RequestParam("secretKeyId") Long secretKeyId) {
|
||||
return secretKeyService.deleteSecretKey(secretKeyId) ? ResponseData.success() : ResponseData.error("删除密钥");
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary ="批量删除密钥")
|
||||
public ResponseData deleteList(@RequestParam("secretKeyId") List<Long> secretKeyIds) {
|
||||
return secretKeyService.deleteList(secretKeyIds) ? ResponseData.success() : ResponseData.error("批量删除密钥");
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary ="获取密钥信息列表")
|
||||
public ResponseData list(@RequestBody SecretKeyDto secretKeyDto) {
|
||||
return ResponseData.success(secretKeyService.listSecretKey(secretKeyDto));
|
||||
}
|
||||
|
||||
@GetMapping("/downloadSecretKeyFile")
|
||||
@Operation(summary ="下载密钥文件")
|
||||
public ResponseEntity<InputStreamResource> downloadSecretKeyFile(@RequestParam("secretKeyId") Long secretKeyId) {
|
||||
return secretKeyService.downloadSecretKeyFile(secretKeyId);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.casic.machine.dto;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 环境变量数据传输对象
|
||||
*/
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MachineEnvDTO extends PageDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String envKey;
|
||||
private String envValue;
|
||||
private Boolean sensitive;
|
||||
private String description;
|
||||
private Long machineInfoId;
|
||||
private Date createDate;
|
||||
private Date updateDate;
|
||||
private String sortField;
|
||||
private String sortDirection;
|
||||
|
||||
|
||||
/**
|
||||
* 获取脱敏后的环境变量值
|
||||
*/
|
||||
public String getMaskedValue() {
|
||||
if (!sensitive || envValue == null) {
|
||||
return envValue;
|
||||
}
|
||||
int length = envValue.length();
|
||||
if (length <= 4) {
|
||||
return "****";
|
||||
} else {
|
||||
return envValue.substring(0, 2) + "****" + envValue.substring(length - 2);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.casic.machine.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MachineInfoDto extends PageDto {
|
||||
private Long id;
|
||||
|
||||
private Date createDate;
|
||||
|
||||
private Date updateDate;
|
||||
|
||||
private String name;
|
||||
|
||||
private String tag;
|
||||
|
||||
private String hostIp;
|
||||
|
||||
private String description;
|
||||
|
||||
private String username;
|
||||
|
||||
private String status;
|
||||
|
||||
private String sshPort;
|
||||
|
||||
private String password;
|
||||
|
||||
private Long secretKeyId;
|
||||
|
||||
private Long machineProxyId;
|
||||
|
||||
private String authenticationType;
|
||||
|
||||
private String machineInfoTypeCode;
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.casic.machine.dto;
|
||||
import com.casic.machine.enums.MachineProxyStatus;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 机器代理数据传输对象
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MachineProxyDTO extends PageDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String username;
|
||||
private String proxyType;
|
||||
private String version;
|
||||
private String status;
|
||||
private Date lastHeartbeatTime;
|
||||
private String config;
|
||||
private String description;
|
||||
private String hostIp;
|
||||
private String sshPort;
|
||||
|
||||
/**
|
||||
* 计算代理是否在线
|
||||
*/
|
||||
public boolean isOnline() {
|
||||
if (status == null || lastHeartbeatTime == null) {
|
||||
return false;
|
||||
}
|
||||
// 假设5分钟内有心跳为在线
|
||||
long fiveMinutes = 5 * 60 * 1000;
|
||||
return Objects.equals(MachineProxyStatus.ONLINE.getMessage(), status) &&
|
||||
System.currentTimeMillis() - lastHeartbeatTime.getTime() < fiveMinutes;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.casic.machine.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PageDto{
|
||||
private int pageIndex = 1;
|
||||
|
||||
private int pageSize = 10;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.casic.machine.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SecretKeyDto extends PageDto {
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
//存储路径
|
||||
private String path;
|
||||
|
||||
//密钥文件
|
||||
private MultipartFile file;
|
||||
|
||||
private String fileName;
|
||||
|
||||
//密钥密码
|
||||
private String password;
|
||||
|
||||
private Date createDate;
|
||||
|
||||
private Date updateDate;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.casic.machine.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BaseEntity {
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
@TableField(value = "create_date")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createDate;
|
||||
|
||||
@TableField(value = "update_date")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateDate;
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.casic.machine.entity;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 环境变量实体类
|
||||
*/
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("machine_env")
|
||||
public class MachineEnv extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
/**
|
||||
* 机器ID(唯一关联)
|
||||
*/
|
||||
private Long machineId;
|
||||
|
||||
/**
|
||||
* 环境变量键
|
||||
*/
|
||||
private String envKey;
|
||||
|
||||
/**
|
||||
* 环境变量值
|
||||
*/
|
||||
private String envValue;
|
||||
|
||||
/**
|
||||
* 是否敏感
|
||||
*/
|
||||
private Boolean sensitive;
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
|
||||
/**
|
||||
* 逻辑删除标志
|
||||
*/
|
||||
@TableLogic
|
||||
private Boolean deleted;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.casic.machine.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.casic.machine.enums.AuthenticationType;
|
||||
import com.casic.machine.enums.MachineInfoStatus;
|
||||
import com.casic.machine.enums.MachineInfoType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName(value = "machine_info")
|
||||
public class MachineInfo extends BaseEntity{
|
||||
@TableField(value = "name")
|
||||
private String name;
|
||||
|
||||
@TableField(value = "tag")
|
||||
private String tag;
|
||||
|
||||
@TableField(value = "host_ip")
|
||||
private String hostIp;
|
||||
|
||||
@TableField(value = "description")
|
||||
private String description;
|
||||
|
||||
@TableField(exist = false)
|
||||
private MachineInfoType machineInfoType;
|
||||
|
||||
@TableField(value = "machine_info_type_code")
|
||||
private int machineInfoTypeCode;
|
||||
|
||||
|
||||
@TableField(exist = false)
|
||||
private MachineInfoStatus status;
|
||||
|
||||
@TableField(value = "status_code")
|
||||
private int statusCode;
|
||||
|
||||
//用户名
|
||||
@TableField(value = "username")
|
||||
private String username;
|
||||
|
||||
//SSH端口号
|
||||
@TableField(value = "SSH_port")
|
||||
private Integer sshPort;
|
||||
|
||||
@TableField(value = "password")
|
||||
private String password;
|
||||
|
||||
@TableField(value = "secret_key_id")
|
||||
private Long secretKeyId;
|
||||
|
||||
@TableField(value = "machine_proxy_id")
|
||||
private Long machineProxyId;
|
||||
|
||||
@TableField(value = "authentication_type_code")
|
||||
private int authenticationTypeCode;
|
||||
|
||||
private AuthenticationType authenticationType;
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.casic.machine.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.casic.machine.enums.MachineInfoStatus;
|
||||
import com.casic.machine.enums.MachineProxyType;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 机器代理实体类
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("machine_proxy")
|
||||
public class MachineProxy extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String hostIp;
|
||||
|
||||
private String sshPort;
|
||||
|
||||
/**
|
||||
* 代理类型
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private MachineProxyType proxyType;
|
||||
|
||||
private int proxyTypeCode;
|
||||
|
||||
/**
|
||||
* 代理版本
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 代理状态 (online, offline, installing, updating, error)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private MachineInfoStatus status;
|
||||
|
||||
|
||||
private int statusCode;
|
||||
|
||||
/**
|
||||
* 最后心跳时间
|
||||
*/
|
||||
private Date lastHeartbeatTime;
|
||||
|
||||
/**
|
||||
* 代理配置 (JSON格式)
|
||||
*/
|
||||
private String config;
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
|
||||
/**
|
||||
* 逻辑删除标志
|
||||
*/
|
||||
@TableLogic
|
||||
private Boolean deleted;
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.casic.machine.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName(value = "machine_secret_key")
|
||||
public class SecretKey extends BaseEntity{
|
||||
@TableField(value = "name")
|
||||
private String name;
|
||||
|
||||
@TableField(value = "description")
|
||||
private String description;
|
||||
|
||||
//存储路径
|
||||
@TableField(value = "path")
|
||||
private String path;
|
||||
|
||||
@TableField
|
||||
private String fileName;
|
||||
|
||||
//密钥密码
|
||||
@TableField(value = "password")
|
||||
private String password;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AuthenticationType implements CodeEnum {
|
||||
PASSWORD(1,"密码认证"),
|
||||
SECRET_KEY(2,"密钥认证");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
public interface CodeEnum {
|
||||
int getCode();
|
||||
|
||||
String getMessage();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 连接状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ConnectionStatus {
|
||||
DISCONNECTED("断开连接"),
|
||||
CONNECTING("正在连接"),
|
||||
CONNECTED("已连接"),
|
||||
AUTH_FAILED("认证失败"),
|
||||
CONNECTION_TIMEOUT("连接超时"),
|
||||
CONNECTION_ERROR("连接错误"),
|
||||
CLOSED("已关闭");
|
||||
|
||||
private final String description;
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineInfoStatus implements CodeEnum {
|
||||
ENABLE(1,"启用"),
|
||||
UN_ENABLE(0,"停用");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineInfoType implements CodeEnum {
|
||||
Linux(1,"Linux"),
|
||||
WINDOWS(2,"Windows");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineProxyStatus implements CodeEnum {
|
||||
/**
|
||||
* 代理状态 (online, offline, installing, updating, error)
|
||||
*/
|
||||
ONLINE(1,"online"),
|
||||
OFFLINE(2,"offline"),
|
||||
INSTALLING(3,"installing"),
|
||||
UPDATING(4,"updating"),
|
||||
ERROR(5,"error");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineProxyType implements CodeEnum {
|
||||
HTTP(1,"http"),
|
||||
SOCKS4(2,"socks4"),
|
||||
SOCKS5(3,"socks5");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PermissionExceptionEnum implements CodeEnum {
|
||||
URL_NOT_EXIST(1, "资源路径不存在,请检查请求地址"),
|
||||
|
||||
NO_PERMISSION(2, "没有权限访问资源,请联系管理员"),
|
||||
|
||||
NO_PERMISSION_OPERATE(3, "没有权限操作该数据,请联系管理员");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.casic.machine.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RequestExceptionEnum implements CodeEnum {
|
||||
REQUEST_TYPE_NOT_JSON(1,"传递参数格式错误,请使用json格式"),
|
||||
REQUEST_JSON_ERROR(2,"json格式错误"),
|
||||
REQUEST_METHOD_NOT_POST(3, "不支持该请求方法,请求方法应为POST"),
|
||||
REQUEST_METHOD_NOT_GET(4, "不支持该请求方法,请求方法应为GET"),
|
||||
PARAM_ERROR(5, "参数错误");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.casic.machine.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 业务逻辑异常 Exception
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ServiceException extends RuntimeException {
|
||||
//机器信息为空
|
||||
public static final int MACHINE_INFO_NULL = 555;
|
||||
//上传文件失败
|
||||
public static final int UPLOADING_FILE_FAIL = 556;
|
||||
//下载失败
|
||||
public static final int DOWNLOAD_FILE_FAIL = 557;
|
||||
//文件名为空
|
||||
public static final int FILENAME_NULL = 558;
|
||||
//读取文件失败
|
||||
public static final int READ_FILE_FAIL = 559;
|
||||
//删除文件失败
|
||||
public static final int DELETE_FILE_FAIL = 560;
|
||||
//MachineProxyDTO对象为空
|
||||
public static final int MACHINE_PROXY_DTO_NULL = 561;
|
||||
//MachineProxy代理不存在
|
||||
public static final int MACHINE_PROXY_NULL = 562;
|
||||
//参数错误
|
||||
public static final int PARAMETER_ERROR = 563;
|
||||
//机器环境变量为空
|
||||
public static final int MACHINE_ENV_NULL = 564;
|
||||
//机器环境变量键不合法
|
||||
public static final int MACHINE_ENV_KEY_ILLEGAL = 565;
|
||||
//oss参数无法读取
|
||||
public static final int OSS_PARAM_NULL = 1001;
|
||||
|
||||
/**
|
||||
* 业务错误码
|
||||
* 区间
|
||||
* machine-management-module模块(555-1000)
|
||||
* common-module模块(1001-2000)
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String message;
|
||||
}
|
@ -0,0 +1,615 @@
|
||||
package com.casic.machine.handler;
|
||||
|
||||
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import com.casic.machine.entity.SecretKey;
|
||||
import com.casic.machine.enums.AuthenticationType;
|
||||
import com.casic.machine.enums.ConnectionStatus;
|
||||
import com.casic.machine.service.SecretKeyService;
|
||||
import com.casic.machine.utils.AliOssUtil;
|
||||
import com.jcraft.jsch.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* 优化后的SSH连接会话类
|
||||
*/
|
||||
@Slf4j
|
||||
public class ConnectionSession implements AutoCloseable {
|
||||
@Resource
|
||||
SecretKeyService secretKeyService;
|
||||
|
||||
@Resource
|
||||
AliOssUtil aliOssUtil;
|
||||
|
||||
private final MachineInfo machineInfo;
|
||||
private Session sshSession;
|
||||
private ConnectionStatus status = ConnectionStatus.DISCONNECTED;
|
||||
private final AtomicBoolean isExecuting = new AtomicBoolean(false);
|
||||
|
||||
// 连接配置常量
|
||||
private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒)
|
||||
private static final int COMMAND_TIMEOUT = 30000; // 命令执行超时时间(毫秒)
|
||||
private static final int RETRY_COUNT = 3; // 重试次数
|
||||
private static final int RETRY_DELAY = 1000; // 重试间隔(毫秒)
|
||||
|
||||
public ConnectionSession(MachineInfo machineInfo) {
|
||||
this.machineInfo = Objects.requireNonNull(machineInfo, "MachineInfo cannot be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立SSH连接,支持重试机制
|
||||
*/
|
||||
public synchronized void connect() throws JSchException {
|
||||
if (status == ConnectionStatus.CONNECTED) {
|
||||
log.debug("Already connected to {}", machineInfo.getHostIp());
|
||||
return;
|
||||
}
|
||||
|
||||
status = ConnectionStatus.CONNECTING;
|
||||
JSchException lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= RETRY_COUNT; attempt++) {
|
||||
try {
|
||||
doConnect();
|
||||
status = ConnectionStatus.CONNECTED;
|
||||
log.info("SSH connection established successfully to {} (attempt {}/{})",
|
||||
machineInfo.getHostIp(), attempt, RETRY_COUNT);
|
||||
return;
|
||||
} catch (JSchException e) {
|
||||
lastException = e;
|
||||
status = ConnectionStatus.CONNECTION_ERROR;
|
||||
log.error("SSH connection attempt {}/{} failed: {}",
|
||||
attempt, RETRY_COUNT, e.getMessage());
|
||||
|
||||
// 认证失败直接退出,无需重试
|
||||
if (e.getMessage().contains("Auth fail")) {
|
||||
status = ConnectionStatus.AUTH_FAILED;
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 重试前等待
|
||||
if (attempt < RETRY_COUNT) {
|
||||
try {
|
||||
Thread.sleep(RETRY_DELAY);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new JSchException("Connection attempt interrupted", ie);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 所有重试都失败
|
||||
throw new JSchException("Failed to connect after " + RETRY_COUNT + " attempts", lastException);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际执行连接逻辑
|
||||
*/
|
||||
private void doConnect() throws JSchException, IOException {
|
||||
JSch jsch = new JSch();
|
||||
|
||||
// 配置认证方式
|
||||
configureAuthentication(jsch);
|
||||
|
||||
// 创建SSH会话
|
||||
sshSession = jsch.getSession(
|
||||
machineInfo.getUsername(),
|
||||
machineInfo.getHostIp(),
|
||||
machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22
|
||||
);
|
||||
|
||||
// 配置连接参数
|
||||
configureSession(sshSession);
|
||||
|
||||
// 建立连接
|
||||
sshSession.connect(CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置认证方式(密码或密钥)
|
||||
*/
|
||||
private void configureAuthentication(JSch jsch) throws JSchException{
|
||||
if (machineInfo.getAuthenticationType() == AuthenticationType.SECRET_KEY) {
|
||||
// 密钥认证
|
||||
if (machineInfo.getSecretKeyId() == null) {
|
||||
throw new JSchException("Secret key ID is required for key-based authentication");
|
||||
}
|
||||
|
||||
String privateKeyContent = getPrivateKeyContent(machineInfo.getSecretKeyId());
|
||||
if (StringUtils.isEmpty(privateKeyContent)) {
|
||||
throw new JSchException("Private key content is empty or null for ID: " + machineInfo.getSecretKeyId());
|
||||
}
|
||||
|
||||
// 加载私钥(支持密码短语,可从配置中获取)
|
||||
jsch.addIdentity(
|
||||
machineInfo.getName(),
|
||||
privateKeyContent.getBytes(StandardCharsets.UTF_8),
|
||||
null,
|
||||
null // 密码短语,可为null
|
||||
);
|
||||
} else if (machineInfo.getAuthenticationType() == AuthenticationType.PASSWORD) {
|
||||
// 密码认证
|
||||
if (StringUtils.isEmpty(machineInfo.getPassword())) {
|
||||
throw new JSchException("Password is required for password-based authentication");
|
||||
}
|
||||
} else {
|
||||
throw new JSchException("Unsupported authentication type: " + machineInfo.getAuthenticationType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置SSH会话参数(安全增强)
|
||||
*/
|
||||
private void configureSession(Session session) {
|
||||
Properties config = new Properties();
|
||||
|
||||
// 安全增强:默认验证主机密钥
|
||||
if (isTrustedEnvironment()) {
|
||||
log.warn("Running in trusted environment - disabling strict host key checking for {}",
|
||||
machineInfo.getHostIp());
|
||||
config.put("StrictHostKeyChecking", "no");
|
||||
} else {
|
||||
config.put("StrictHostKeyChecking", "yes");
|
||||
// 可选:配置已知主机文件路径
|
||||
//直接配置阿里云密钥地址
|
||||
config.put("UserKnownHostsFile", secretKeyService.getById(machineInfo.getSecretKeyId()).getPath());
|
||||
}
|
||||
|
||||
// 其他安全配置
|
||||
config.put("PreferredAuthentications", "publicKey,password,keyboard-interactive");
|
||||
config.put("ServerAliveInterval", "30"); // 每30秒发送一次心跳
|
||||
config.put("ServerAliveCountMax", "3"); // 允许3次心跳失败
|
||||
|
||||
session.setConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为可信环境(生产环境应返回false)
|
||||
*/
|
||||
private boolean isTrustedEnvironment() {
|
||||
// todo实际项目中应基于配置或环境变量判断
|
||||
return System.getProperty("environment", "production").equalsIgnoreCase("development");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
public synchronized void disconnect() {
|
||||
if (sshSession != null && sshSession.isConnected()) {
|
||||
try {
|
||||
sshSession.disconnect();
|
||||
log.info("SSH connection closed: {}", machineInfo.getHostIp());
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing SSH session: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
status = ConnectionStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行远程命令,支持超时和中断处理
|
||||
*/
|
||||
public String executeCommand(String command) throws JSchException, IOException {
|
||||
if (!isConnected()) {
|
||||
throw new IllegalStateException("Session is not connected");
|
||||
}
|
||||
|
||||
if (!isExecuting.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException("Another command is already executing");
|
||||
}
|
||||
|
||||
Channel channel = null;
|
||||
InputStream inputStream = null;
|
||||
ByteArrayOutputStream outputStream = null;
|
||||
|
||||
try {
|
||||
channel = createExecChannel(command);
|
||||
inputStream = channel.getInputStream();
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
// 连接通道并设置超时
|
||||
channel.connect(COMMAND_TIMEOUT);
|
||||
|
||||
// 读取命令输出
|
||||
return readCommandOutput(inputStream, outputStream, channel);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Command execution interrupted", e);
|
||||
} finally {
|
||||
// 释放资源
|
||||
closeResources(channel, inputStream, outputStream);
|
||||
isExecuting.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并配置命令执行通道
|
||||
*/
|
||||
private Channel createExecChannel(String command) throws JSchException {
|
||||
Channel channel = sshSession.openChannel("exec");
|
||||
((ChannelExec) channel).setCommand(command);
|
||||
|
||||
// 配置通道
|
||||
channel.setInputStream(null);
|
||||
((ChannelExec) channel).setErrStream(new ByteArrayOutputStream()); // 捕获错误输出
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取命令输出
|
||||
*/
|
||||
private String readCommandOutput(InputStream inputStream,
|
||||
ByteArrayOutputStream outputStream,
|
||||
Channel channel) throws IOException, InterruptedException {
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
|
||||
// 使用线程中断机制实现超时控制
|
||||
Thread readerThread = new Thread(() -> {
|
||||
int bytesRead ;
|
||||
try {
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 通道关闭或读取异常
|
||||
if (channel.isConnected()) {
|
||||
log.warn("Error reading command output: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
readerThread.start();
|
||||
|
||||
// 等待命令执行完成或超时
|
||||
readerThread.join(COMMAND_TIMEOUT);
|
||||
|
||||
// 如果线程仍在运行,中断并关闭通道
|
||||
if (readerThread.isAlive()) {
|
||||
readerThread.interrupt();
|
||||
channel.disconnect();
|
||||
throw new IOException("Command execution timed out after " + COMMAND_TIMEOUT + "ms");
|
||||
}
|
||||
|
||||
// 等待通道完全关闭
|
||||
while (channel.isConnected()) {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
return outputStream.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭资源
|
||||
*/
|
||||
private void closeResources(Channel channel, InputStream inputStream, OutputStream outputStream) {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing output stream: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (channel != null && channel.isConnected()) {
|
||||
channel.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到远程服务器
|
||||
*/
|
||||
public boolean uploadFile(String localFilePath, String remoteFilePath) throws IOException {
|
||||
if (!isConnected()) {
|
||||
throw new IllegalStateException("Cannot upload file: SSH session is not connected");
|
||||
}
|
||||
|
||||
// 检查本地文件是否存在且可读
|
||||
File localFile = new File(localFilePath);
|
||||
if (!localFile.exists()) {
|
||||
throw new FileNotFoundException("Local file not found: " + localFilePath);
|
||||
}
|
||||
if (!localFile.canRead()) {
|
||||
throw new IOException("Cannot read local file: " + localFilePath);
|
||||
}
|
||||
|
||||
ChannelSftp channel = null;
|
||||
boolean uploadSuccess = false;
|
||||
|
||||
try {
|
||||
// 创建并连接SFTP通道,设置超时
|
||||
channel = (ChannelSftp) sshSession.openChannel("sftp");
|
||||
channel.connect(CONNECTION_TIMEOUT);
|
||||
|
||||
// 确保目标目录存在
|
||||
createRemoteDirectoryIfNotExists(channel, getParentDirectory(remoteFilePath));
|
||||
|
||||
// 使用更健壮的上传方式
|
||||
channel.put(
|
||||
new FileInputStream(localFile),
|
||||
remoteFilePath,
|
||||
new ProgressMonitorAdapter(localFile.length()),
|
||||
ChannelSftp.OVERWRITE
|
||||
);
|
||||
|
||||
uploadSuccess = true;
|
||||
log.info("File uploaded successfully: {} -> {}", localFilePath, remoteFilePath);
|
||||
return true;
|
||||
|
||||
} catch (SftpException e) {
|
||||
log.error("SFTP error during file upload ({} -> {}): {}",
|
||||
localFilePath, remoteFilePath, e.getMessage(), e);
|
||||
throw new IOException("SFTP error: " + e.getMessage(), e);
|
||||
} catch (FileNotFoundException e) {
|
||||
log.error("Local file not found during upload: {}", localFilePath, e);
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
log.error("IO error during file upload: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during file upload: {}", e.getMessage(), e);
|
||||
throw new IOException("Unexpected error: " + e.getMessage(), e);
|
||||
} finally {
|
||||
// 确保通道始终被关闭
|
||||
disconnectChannel(channel);
|
||||
|
||||
// // 如果上传失败,尝试删除不完整的文件
|
||||
// if (!uploadSuccess && remoteFilePath != null && !remoteFilePath.isEmpty()) {
|
||||
// tryDeleteIncompleteFile(remoteFilePath);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public boolean downloadFile(String remoteFilePath, String localFilePath) throws IOException {
|
||||
if (!isConnected()) {
|
||||
throw new IllegalStateException("Cannot download file: SSH session is not connected");
|
||||
}
|
||||
|
||||
// 检查本地目录是否存在且可写
|
||||
File localFile = new File(localFilePath);
|
||||
File parentDir = localFile.getParentFile();
|
||||
if (parentDir != null && !parentDir.exists()) {
|
||||
if (!parentDir.mkdirs()) {
|
||||
throw new IOException("Failed to create local directory: " + parentDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
if (parentDir != null && !parentDir.canWrite()) {
|
||||
throw new IOException("Cannot write to local directory: " + parentDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
ChannelSftp channel = null;
|
||||
boolean downloadSuccess = false;
|
||||
File tempFile = null;
|
||||
|
||||
try {
|
||||
// 创建并连接SFTP通道,设置超时
|
||||
channel = (ChannelSftp) sshSession.openChannel("sftp");
|
||||
channel.connect(CONNECTION_TIMEOUT);
|
||||
|
||||
// 检查远程文件是否存在
|
||||
SftpATTRS attrs = channel.stat(remoteFilePath);
|
||||
long fileSize = attrs.getSize();
|
||||
|
||||
// 使用临时文件避免部分下载覆盖完整文件
|
||||
tempFile = new File(localFilePath + ".part");
|
||||
|
||||
// 执行下载并监控进度
|
||||
channel.get(
|
||||
remoteFilePath,
|
||||
new FileOutputStream(tempFile).toString(),
|
||||
new ProgressMonitorAdapter(fileSize),
|
||||
ChannelSftp.OVERWRITE
|
||||
);
|
||||
|
||||
// 验证下载完整性
|
||||
if (tempFile.length() != fileSize) {
|
||||
throw new IOException("Download incomplete: expected " + fileSize +
|
||||
" bytes, but got " + tempFile.length() + " bytes");
|
||||
}
|
||||
|
||||
// 重命名临时文件为目标文件(原子操作)
|
||||
if (!tempFile.renameTo(localFile)) {
|
||||
throw new IOException("Failed to rename temporary file to: " + localFilePath);
|
||||
}
|
||||
|
||||
downloadSuccess = true;
|
||||
log.info("File downloaded successfully: {} -> {}", remoteFilePath, localFilePath);
|
||||
return true;
|
||||
|
||||
} catch (SftpException e) {
|
||||
log.error("SFTP error during file download ({} -> {}): {}",
|
||||
remoteFilePath, localFilePath, e.getMessage(), e);
|
||||
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
|
||||
throw new FileNotFoundException("Remote file not found: " + remoteFilePath);
|
||||
}
|
||||
throw new IOException("SFTP error: " + e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
log.error("IO error during file download: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during file download: {}", e.getMessage(), e);
|
||||
throw new IOException("Unexpected error: " + e.getMessage(), e);
|
||||
} finally {
|
||||
// 确保通道始终被关闭
|
||||
disconnectChannel(channel);
|
||||
|
||||
// 如果下载失败,清理临时文件
|
||||
if (!downloadSuccess && tempFile != null && tempFile.exists()) {
|
||||
if (tempFile.delete()) {
|
||||
log.debug("Deleted incomplete temporary file: {}", tempFile.getAbsolutePath());
|
||||
} else {
|
||||
log.warn("Failed to delete incomplete temporary file: {}", tempFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建远程目录(如果不存在)
|
||||
private void createRemoteDirectoryIfNotExists(ChannelSftp channel, String directory) throws SftpException {
|
||||
if (directory == null || directory.isEmpty() || directory.equals("/")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
channel.stat(directory);
|
||||
} catch (SftpException e) {
|
||||
// 目录不存在,尝试创建
|
||||
createRemoteDirectoryIfNotExists(channel, getParentDirectory(directory));
|
||||
channel.mkdir(directory);
|
||||
log.debug("Created remote directory: {}", directory);
|
||||
}
|
||||
}
|
||||
// 获取路径的父目录
|
||||
private String getParentDirectory(String path) {
|
||||
int lastSlash = path.lastIndexOf('/');
|
||||
return lastSlash > 0 ? path.substring(0, lastSlash) : "";
|
||||
}
|
||||
// 断开SFTP通道
|
||||
private void disconnectChannel(Channel channel) {
|
||||
if (channel != null && channel.isConnected()) {
|
||||
try {
|
||||
channel.disconnect();
|
||||
log.debug("SFTP channel disconnected");
|
||||
} catch (Exception e) {
|
||||
log.warn("Error disconnecting SFTP channel: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试删除不完整的文件
|
||||
private void tryDeleteIncompleteFile(String remoteFilePath) {
|
||||
ChannelSftp channel = null;
|
||||
try {
|
||||
channel = (ChannelSftp) sshSession.openChannel("sftp");
|
||||
channel.connect(CONNECTION_TIMEOUT);
|
||||
channel.rm(remoteFilePath);
|
||||
log.info("Deleted incomplete file: {}", remoteFilePath);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to delete incomplete file {}: {}", remoteFilePath, e.getMessage());
|
||||
} finally {
|
||||
disconnectChannel(channel);
|
||||
}
|
||||
}
|
||||
// 增强的进度监控器
|
||||
private static class ProgressMonitorAdapter implements SftpProgressMonitor {
|
||||
private final long totalBytes;
|
||||
private long bytesWritten = 0;
|
||||
private int lastProgress = 0;
|
||||
private final long startTime = System.currentTimeMillis();
|
||||
|
||||
public ProgressMonitorAdapter(long totalBytes) {
|
||||
this.totalBytes = totalBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean count(long count) {
|
||||
bytesWritten += count;
|
||||
|
||||
// 计算进度百分比
|
||||
int progress = (int) ((bytesWritten * 100) / totalBytes);
|
||||
|
||||
// 每10%或每秒更新一次日志
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
if (progress - lastProgress >= 10 || elapsedTime >= 1000) {
|
||||
double speed = bytesWritten / (elapsedTime / 1000.0);
|
||||
String speedStr = formatTransferSpeed(speed);
|
||||
|
||||
log.debug("Upload progress: {}% ({}/{} bytes, {})",
|
||||
progress, bytesWritten, totalBytes, speedStr);
|
||||
lastProgress = progress;
|
||||
}
|
||||
|
||||
return true; // 返回true继续传输,返回false中断传输
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
double speed = totalBytes / (elapsedTime / 1000.0);
|
||||
String speedStr = formatTransferSpeed(speed);
|
||||
|
||||
log.info("Upload completed: {} bytes in {} ms (avg speed: {})",
|
||||
totalBytes, elapsedTime, speedStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(int op, String src, String dest, long max) {
|
||||
log.info("Starting upload: {} -> {} ({} bytes)", src, dest, max);
|
||||
}
|
||||
|
||||
// 格式化传输速度
|
||||
private String formatTransferSpeed(double bytesPerSecond) {
|
||||
String[] units = {"B/s", "KB/s", "MB/s", "GB/s"};
|
||||
int unitIndex = 0;
|
||||
|
||||
while (bytesPerSecond >= 1024 && unitIndex < units.length - 1) {
|
||||
bytesPerSecond /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return String.format("%.2f %s", bytesPerSecond, units[unitIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查连接状态
|
||||
*/
|
||||
public ConnectionStatus getStatus() {
|
||||
if (status == ConnectionStatus.CONNECTED &&
|
||||
(sshSession == null || !sshSession.isConnected())) {
|
||||
status = ConnectionStatus.DISCONNECTED;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已连接
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return status == ConnectionStatus.CONNECTED &&
|
||||
sshSession != null &&
|
||||
sshSession.isConnected();
|
||||
}
|
||||
|
||||
|
||||
private String getPrivateKeyContent(Long secretKeyId) {
|
||||
if (secretKeyId == null) {
|
||||
return null;
|
||||
}
|
||||
SecretKey secretKey = secretKeyService.getById(secretKeyId);
|
||||
InputStream read = aliOssUtil.read(secretKey.getFileName());
|
||||
try {
|
||||
return StreamUtils.copyToString(read, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
log.error("读取私钥文件失败", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
package com.casic.machine.handler;
|
||||
|
||||
|
||||
import com.casic.machine.contants.CommonConstants;
|
||||
import com.casic.machine.enums.PermissionExceptionEnum;
|
||||
import com.casic.machine.enums.RequestExceptionEnum;
|
||||
import com.casic.machine.pojo.ErrorResponseData;
|
||||
import com.casic.machine.pojo.ResponseData;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
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.naming.AuthenticationException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Order(-200)
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 请求参数异常
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleMissingParameterException(MissingServletRequestParameterException e) {
|
||||
logger.error("请求参数异常{}", e.getMessage());
|
||||
return renderErrorJson(
|
||||
500,
|
||||
String.format("缺少请求参数{%s},类型为{%s}", e.getParameterName(), e.getParameterType()),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截参数格式传递异常
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||
logger.error("参数格式传递异常{}",e.getMessage());
|
||||
return renderErrorJson(
|
||||
RequestExceptionEnum.REQUEST_JSON_ERROR.getCode(),
|
||||
RequestExceptionEnum.REQUEST_JSON_ERROR.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截不支持媒体类型异常
|
||||
*/
|
||||
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
|
||||
logger.error("不支持媒体类型异常{}",e.getMessage());
|
||||
return renderErrorJson(
|
||||
RequestExceptionEnum.REQUEST_TYPE_NOT_JSON.getCode(),
|
||||
RequestExceptionEnum.REQUEST_TYPE_NOT_JSON.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截请求方法异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleHttpRequestMethodNotSupportedException(HttpServletRequest httpServletRequest) {
|
||||
if ("GET".equalsIgnoreCase(httpServletRequest.getMethod())) {
|
||||
logger.error("请求方法异常{}",RequestExceptionEnum.REQUEST_METHOD_NOT_POST.getMessage());
|
||||
return renderErrorJson(
|
||||
RequestExceptionEnum.REQUEST_METHOD_NOT_POST.getCode(),
|
||||
RequestExceptionEnum.REQUEST_METHOD_NOT_POST.getMessage(),
|
||||
null
|
||||
);
|
||||
}
|
||||
if("POST".equalsIgnoreCase(httpServletRequest.getMethod())) {
|
||||
logger.error("请求方法异常{}",RequestExceptionEnum.REQUEST_METHOD_NOT_GET.getMessage());
|
||||
return renderErrorJson(
|
||||
RequestExceptionEnum.REQUEST_METHOD_NOT_GET.getCode(),
|
||||
RequestExceptionEnum.REQUEST_METHOD_NOT_GET.getMessage(),
|
||||
null
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截资源找不到的运行时异常
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleNoHandlerFoundException(NoHandlerFoundException e) {
|
||||
logger.error("资源不存在异常{}",e.getMessage());
|
||||
return renderErrorJson(
|
||||
PermissionExceptionEnum.URL_NOT_EXIST.getCode(),
|
||||
PermissionExceptionEnum.NO_PERMISSION.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截参数校验错误异常,JSON传参
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
logger.error("拦截参数校验错误异常,JSON传参{}",e.getMessage());
|
||||
return renderErrorJson(
|
||||
RequestExceptionEnum.PARAM_ERROR.getCode(),
|
||||
getArgNotValidMessage(e.getBindingResult()),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截认证失败异常
|
||||
*/
|
||||
@ExceptionHandler(AuthenticationException.class)
|
||||
@ResponseBody
|
||||
public ResponseData handleAuthenticationException(AuthenticationException e) {
|
||||
logger.error("拦截认证失败异常{}",e.getMessage());
|
||||
return renderErrorJson(
|
||||
500,
|
||||
e.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* 渲染异常Json
|
||||
*/
|
||||
private ErrorResponseData renderErrorJson(int code, String msg,Throwable e) {
|
||||
if (e != null) {
|
||||
StackTraceElement[] stackTrace = e.getStackTrace();
|
||||
|
||||
//默认的异常类全路径为第一条异常堆栈信息的
|
||||
String exceptionClassTotalName = stackTrace[0].toString();
|
||||
|
||||
//遍历所有堆栈信息,找到cd.casic开头的第一条异常信息
|
||||
for (StackTraceElement stackTraceElement : stackTrace) {
|
||||
if (stackTraceElement.toString().contains(CommonConstants.DEFAULT_PACKAGE_NAME)) {
|
||||
exceptionClassTotalName = stackTraceElement.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
ErrorResponseData error = ResponseData.error(code, msg);
|
||||
error.setExceptionClazz(exceptionClassTotalName);
|
||||
return error;
|
||||
}else {
|
||||
return ResponseData.error(code, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数不正确的提示信息
|
||||
* 多个信息,拼接成用逗号分隔的形式
|
||||
*/
|
||||
private String getArgNotValidMessage(BindingResult bindingResult) {
|
||||
if (bindingResult == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
//多个错误用逗号分隔
|
||||
return bindingResult.getAllErrors().stream()
|
||||
.map(ObjectError::getDefaultMessage)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.casic.machine.mapper;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
import com.casic.machine.entity.MachineEnv;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 环境变量Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface MachineEnvMapper extends BaseMapper<MachineEnv> {
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.casic.machine.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MachineInfoMapper extends BaseMapper<MachineInfo> {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.casic.machine.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.casic.machine.entity.MachineProxy;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 机器代理Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface MachineProxyMapper extends BaseMapper<MachineProxy> {
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.casic.machine.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.casic.machine.entity.SecretKey;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SecretServiceMapper extends BaseMapper<SecretKey> {
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.casic.machine.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class ErrorResponseData extends ResponseData {
|
||||
|
||||
/**
|
||||
* 异常的具体类名称
|
||||
*/
|
||||
private String exceptionClazz;
|
||||
|
||||
public ErrorResponseData(String message) {
|
||||
super(false, DEFAULT_ERROR_CODE, message, null);
|
||||
}
|
||||
|
||||
public ErrorResponseData(Integer code, String message) {
|
||||
super(false, code, message, null);
|
||||
}
|
||||
|
||||
public ErrorResponseData(Integer code, String message, Object object) {
|
||||
super(false, code, message, object);
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.casic.machine.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 响应结果数据
|
||||
*/
|
||||
@Data
|
||||
public class ResponseData {
|
||||
|
||||
public static final String DEFAULT_SUCCESS_MESSAGE = "请求成功";
|
||||
|
||||
public static final String DEFAULT_ERROR_MESSAGE = "网络异常";
|
||||
|
||||
public static final Integer DEFAULT_SUCCESS_CODE = 200;
|
||||
|
||||
public static final Integer DEFAULT_ERROR_CODE = 500;
|
||||
|
||||
/**
|
||||
* 请求是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* 响应状态码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应对象
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
public ResponseData() {
|
||||
}
|
||||
|
||||
public ResponseData(Boolean success, Integer code, String message, Object data) {
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static SuccessResponseData success() {
|
||||
return new SuccessResponseData();
|
||||
}
|
||||
|
||||
public static SuccessResponseData success(Object object) {
|
||||
return new SuccessResponseData(object);
|
||||
}
|
||||
|
||||
public static SuccessResponseData success(Integer code, String message, Object object) {
|
||||
return new SuccessResponseData(code, message, object);
|
||||
}
|
||||
|
||||
public static ErrorResponseData error(String message) {
|
||||
return new ErrorResponseData(message);
|
||||
}
|
||||
|
||||
public static ErrorResponseData error(Integer code, String message) {
|
||||
return new ErrorResponseData(code, message);
|
||||
}
|
||||
|
||||
public static ErrorResponseData error(Integer code, String message, Object object) {
|
||||
return new ErrorResponseData(code, message, object);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.casic.machine.pojo;
|
||||
|
||||
public class SuccessResponseData extends ResponseData {
|
||||
|
||||
public SuccessResponseData() {
|
||||
super(true, DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MESSAGE, null);
|
||||
}
|
||||
|
||||
public SuccessResponseData(Object object) {
|
||||
super(true, DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MESSAGE, object);
|
||||
}
|
||||
|
||||
public SuccessResponseData(Integer code, String message, Object object) {
|
||||
super(true, code, message, object);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.casic.machine.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.casic.machine.dto.MachineEnvDTO;
|
||||
import com.casic.machine.entity.MachineEnv;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 环境变量服务接口
|
||||
*/
|
||||
|
||||
public interface MachineEnvService extends IService<MachineEnv> {
|
||||
/**
|
||||
* 创建或更新机器的环境变量(一对一关系)
|
||||
*/
|
||||
boolean add(MachineEnvDTO machineEnvDTO);
|
||||
/**
|
||||
* 删除机器的环境变量
|
||||
* @param machineId 机器ID
|
||||
*/
|
||||
void deleteByMachineId(Long machineId);
|
||||
|
||||
/**
|
||||
* 获取机器的环境变量
|
||||
* @param machineId 机器ID
|
||||
* @return 环境变量DTO
|
||||
*/
|
||||
MachineEnvDTO getByMachineId(Long machineId);
|
||||
|
||||
/**
|
||||
* @return 环境变量列表
|
||||
*/
|
||||
PageResult<MachineEnvDTO> listEnv(MachineEnvDTO machineEnvDTO);
|
||||
|
||||
void deleteList(List<Long> ids);
|
||||
|
||||
boolean update(MachineEnvDTO machineEnvDTO);
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.casic.machine.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import com.casic.machine.dto.MachineInfoDto;
|
||||
import com.casic.machine.enums.ConnectionStatus;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface MachineInfoService extends IService<MachineInfo> {
|
||||
boolean addMachineInfo(MachineInfoDto MachineInfoDto);
|
||||
|
||||
PageResult<MachineInfoDto> listMachineInfo(MachineInfoDto MachineInfoDto);
|
||||
|
||||
boolean updateMachineInfo(MachineInfoDto machineInfoDto);
|
||||
|
||||
boolean updateStatus(Long machineInfoId, String status);
|
||||
|
||||
boolean bindingSecretKey( Long machineInfoId, Long secretKeyId);
|
||||
|
||||
void deleteList(List<Long> machineInfoIds);
|
||||
|
||||
void deleteMachineInfo(Long machineInfoId);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 测试机器连接
|
||||
* @param machineInfo 机器信息
|
||||
* @return 连接是否成功
|
||||
*/
|
||||
boolean testConnection(MachineInfo machineInfo);
|
||||
|
||||
/**
|
||||
* 获取机器连接状态
|
||||
* @param machineName 机器名称
|
||||
* @return 连接状态
|
||||
*/
|
||||
ConnectionStatus getConnectionStatus(String machineName);
|
||||
|
||||
/**
|
||||
* 获取所有连接状态
|
||||
* @return 机器名称到连接状态的映射
|
||||
*/
|
||||
Map<String, ConnectionStatus> getAllConnectionStatus();
|
||||
|
||||
/**
|
||||
* 建立机器连接
|
||||
* @param machineInfo 机器信息
|
||||
* @return 连接会话ID
|
||||
*/
|
||||
String connect(MachineInfo machineInfo);
|
||||
|
||||
/**
|
||||
* 断开机器连接
|
||||
* @param sessionId 会话ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
boolean disconnect(String sessionId);
|
||||
|
||||
/**
|
||||
* 执行远程命令
|
||||
* @param sessionId 会话ID
|
||||
* @param command 命令
|
||||
* @return 命令执行结果
|
||||
*/
|
||||
String executeCommand(String sessionId, String command);
|
||||
|
||||
/**
|
||||
* 上传文件到远程机器
|
||||
* @param sessionId 会话ID
|
||||
* @param localFilePath 本地文件路径
|
||||
* @param remoteFilePath 远程文件路径
|
||||
* @return 操作结果
|
||||
*/
|
||||
boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath);
|
||||
|
||||
/**
|
||||
* 从远程机器下载文件
|
||||
* @param sessionId 会话ID
|
||||
* @param remoteFilePath 远程文件路径
|
||||
* @param localFilePath 本地文件路径
|
||||
* @return 操作结果
|
||||
*/
|
||||
boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.casic.machine.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.casic.machine.dto.MachineProxyDTO;
|
||||
import com.casic.machine.entity.MachineProxy;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 机器代理服务接口
|
||||
*/
|
||||
public interface MachineProxyService extends IService<MachineProxy> {
|
||||
/**
|
||||
* 注册新的机器代理
|
||||
*/
|
||||
boolean register(MachineProxyDTO machineProxyDTO);
|
||||
|
||||
/**
|
||||
* 更新代理状态
|
||||
*/
|
||||
boolean updateStatus(MachineProxyDTO machineProxyDTO);
|
||||
|
||||
/**
|
||||
* 接收代理心跳
|
||||
*/
|
||||
boolean heartbeat(MachineProxyDTO machineProxyDTO);
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有代理的状态统计
|
||||
*
|
||||
* @return 状态统计Map
|
||||
*/
|
||||
Map<String, Long> getStatusStatistics();
|
||||
|
||||
/**
|
||||
* 更新代理配置
|
||||
*/
|
||||
boolean updateConfig(MachineProxyDTO machineProxyDTO);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 批量删除代理
|
||||
* @param proxyIds 代理ID列表
|
||||
*/
|
||||
void delete(List<Long> proxyIds);
|
||||
|
||||
PageResult<MachineProxyDTO> list(MachineProxyDTO machineProxyDTO);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.casic.machine.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.casic.machine.entity.SecretKey;
|
||||
import com.casic.machine.dto.SecretKeyDto;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SecretKeyService extends IService<SecretKey> {
|
||||
boolean addSecretKey(SecretKeyDto secretKeyDto, MultipartFile file);
|
||||
|
||||
void bindingMachine(Long secretKeyId, List<Long> machineInfoIds);
|
||||
|
||||
boolean updateSecretKey(SecretKeyDto secretKeyDto,MultipartFile file);
|
||||
|
||||
boolean deleteSecretKey(Long secretKeyId);
|
||||
|
||||
PageResult<SecretKey> listSecretKey(SecretKeyDto secretKeyDto);
|
||||
|
||||
ResponseEntity<InputStreamResource> downloadSecretKeyFile(Long secretKeyId);
|
||||
|
||||
boolean deleteList(List<Long> secretKeyIds);
|
||||
}
|
||||
|
@ -0,0 +1,175 @@
|
||||
package com.casic.machine.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
import com.casic.machine.dto.MachineEnvDTO;
|
||||
import com.casic.machine.entity.MachineEnv;
|
||||
import com.casic.machine.exception.ServiceException;
|
||||
import com.casic.machine.mapper.MachineEnvMapper;
|
||||
import com.casic.machine.service.MachineEnvService;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 环境变量服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class MachineEnvServiceImpl extends ServiceImpl<MachineEnvMapper, MachineEnv> implements MachineEnvService {
|
||||
|
||||
@Resource
|
||||
private MachineEnvMapper machineEnvMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean add(MachineEnvDTO machineEnvDTO) {
|
||||
// 参数校验
|
||||
if (machineEnvDTO==null) {
|
||||
throw new ServiceException(ServiceException.MACHINE_ENV_NULL,"环境变量不能为空");
|
||||
}
|
||||
|
||||
// 检查键是否合法
|
||||
if (!isValidKey(machineEnvDTO.getEnvKey())) {
|
||||
throw new ServiceException(ServiceException.MACHINE_ENV_KEY_ILLEGAL,"环境变量键不合法");
|
||||
}
|
||||
|
||||
// 判断是否敏感变量
|
||||
boolean sensitive = isSensitive(machineEnvDTO.getEnvKey());
|
||||
|
||||
MachineEnv machineEnv = new MachineEnv();
|
||||
BeanUtils.copyProperties(machineEnvDTO, machineEnv);
|
||||
return save(machineEnv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMachineId(Long machineId) {
|
||||
this.removeById(machineId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public MachineEnvDTO getByMachineId(Long machineId) {
|
||||
if (machineId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MachineEnv machineEnv = getOne(
|
||||
new LambdaQueryWrapper<MachineEnv>()
|
||||
.eq(MachineEnv::getMachineId, machineId)
|
||||
);
|
||||
|
||||
return machineEnv != null ? convertToDTO(machineEnv) : null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PageResult<MachineEnvDTO> listEnv(MachineEnvDTO machineEnvDTO) {
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<MachineEnv> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 环境变量键模糊查询
|
||||
if (!StringUtils.isEmpty(machineEnvDTO.getEnvKey())) {
|
||||
queryWrapper.like(MachineEnv::getEnvKey, machineEnvDTO.getEnvKey());
|
||||
}
|
||||
|
||||
// 机器ID模糊查询
|
||||
if (!StringUtils.isEmpty(machineEnvDTO.getMachineInfoId())) {
|
||||
queryWrapper.like(MachineEnv::getMachineId, machineEnvDTO.getMachineInfoId());
|
||||
}
|
||||
|
||||
// 是否敏感
|
||||
if (machineEnvDTO.getSensitive() != null) {
|
||||
queryWrapper.eq(MachineEnv::getSensitive, machineEnvDTO.getSensitive());
|
||||
}
|
||||
|
||||
// 创建时间范围查询
|
||||
if (!StringUtils.isEmpty(machineEnvDTO.getCreateDate())) {
|
||||
queryWrapper.ge(MachineEnv::getCreateDate, machineEnvDTO.getCreateDate());
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (!StringUtils.isEmpty(machineEnvDTO.getSortField())) {
|
||||
boolean isAsc = "asc".equalsIgnoreCase(machineEnvDTO.getSortDirection());
|
||||
switch (machineEnvDTO.getSortField()) {
|
||||
case "envKey":
|
||||
queryWrapper.orderBy(true, isAsc, MachineEnv::getEnvKey);
|
||||
break;
|
||||
case "machineId":
|
||||
queryWrapper.orderBy(true, isAsc, MachineEnv::getMachineId);
|
||||
break;
|
||||
case "createTime":
|
||||
default:
|
||||
queryWrapper.orderBy(true, isAsc, MachineEnv::getCreateDate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
Page<MachineEnv> page = machineEnvMapper.selectPage(new Page<>(machineEnvDTO.getPageIndex(), machineEnvDTO.getPageSize()), queryWrapper);
|
||||
|
||||
// 转换结果
|
||||
List<MachineEnvDTO> dtoList = page.getRecords().stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 构建分页结果
|
||||
return PageResult.<MachineEnvDTO>builder()
|
||||
.pageNum(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.total(page.getTotal())
|
||||
.pages(page.getPages())
|
||||
.list(dtoList)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteList(List<Long> ids) {
|
||||
this.machineEnvMapper.deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean update(MachineEnvDTO machineEnvDTO) {
|
||||
MachineEnv machineEnv = new MachineEnv();
|
||||
BeanUtils.copyProperties(machineEnvDTO,machineEnv);
|
||||
return this.updateById(machineEnv);
|
||||
}
|
||||
|
||||
// 转换实体为DTO
|
||||
private MachineEnvDTO convertToDTO(MachineEnv machineEnv) {
|
||||
MachineEnvDTO dto = new MachineEnvDTO();
|
||||
dto.setId(machineEnv.getId());
|
||||
dto.setEnvKey(machineEnv.getEnvKey());
|
||||
dto.setEnvValue(machineEnv.getEnvValue());
|
||||
dto.setSensitive(machineEnv.getSensitive());
|
||||
dto.setDescription(machineEnv.getDescription());
|
||||
dto.setCreateDate(machineEnv.getCreateDate());
|
||||
dto.setUpdateDate(machineEnv.getUpdateDate());
|
||||
return dto;
|
||||
}
|
||||
|
||||
// 检查环境变量键是否合法
|
||||
private boolean isValidKey(String key) {
|
||||
return key.matches("^[a-zA-Z_][a-zA-Z0-9_]*$");
|
||||
}
|
||||
|
||||
// 判断是否为敏感变量
|
||||
private boolean isSensitive(String key) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
String upperKey = key.toUpperCase();
|
||||
return upperKey.contains("PASSWORD") || upperKey.contains("SECRET") ||
|
||||
upperKey.contains("TOKEN") || upperKey.contains("KEY");
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package com.casic.machine.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
import com.casic.machine.dto.MachineProxyDTO;
|
||||
import com.casic.machine.entity.MachineProxy;
|
||||
import com.casic.machine.enums.MachineProxyStatus;
|
||||
import com.casic.machine.enums.MachineProxyType;
|
||||
import com.casic.machine.exception.ServiceException;
|
||||
import com.casic.machine.mapper.MachineProxyMapper;
|
||||
import com.casic.machine.service.MachineProxyService;
|
||||
import com.casic.machine.utils.EnumUtils;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 机器代理服务实现类
|
||||
*/
|
||||
@Service
|
||||
public class MachineProxyServiceImpl extends ServiceImpl<MachineProxyMapper, MachineProxy> implements MachineProxyService {
|
||||
@Resource
|
||||
private MachineProxyMapper machineProxyMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean register(MachineProxyDTO machineProxyDTO) {
|
||||
// 创建代理记录
|
||||
MachineProxy proxy = new MachineProxy();
|
||||
proxy.setProxyTypeCode(EnumUtils.getEnumByMessage(machineProxyDTO.getProxyType(), MachineProxyType.class).getCode());
|
||||
proxy.setVersion("1.0.0");
|
||||
proxy.setStatusCode(MachineProxyStatus.INSTALLING.getCode());
|
||||
return save(proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateStatus(MachineProxyDTO machineProxyDTO) {
|
||||
// 参数校验
|
||||
if (machineProxyDTO == null) {
|
||||
throw new ServiceException(ServiceException.MACHINE_PROXY_DTO_NULL,"MachineProxyDTO对象为空");
|
||||
}
|
||||
|
||||
// 查询代理
|
||||
MachineProxy proxy = this.getById(machineProxyDTO.getId());
|
||||
|
||||
if (proxy == null) {
|
||||
throw new ServiceException(ServiceException.MACHINE_PROXY_NULL,"代理不存在");
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
proxy.setStatusCode(EnumUtils.getEnumByMessage(machineProxyDTO.getStatus(),MachineProxyStatus.class).getCode());
|
||||
proxy.setUpdateDate(new Date());
|
||||
return updateById(proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean heartbeat(MachineProxyDTO machineProxyDTO) {
|
||||
// 参数校验
|
||||
if (machineProxyDTO == null) {
|
||||
throw new IllegalArgumentException("MachineProxyDTO对象为空");
|
||||
}
|
||||
|
||||
// 查询代理
|
||||
MachineProxy proxy = this.getById(machineProxyDTO.getId());
|
||||
|
||||
if (proxy == null) {
|
||||
throw new IllegalArgumentException("代理不存在");
|
||||
}
|
||||
|
||||
// 更新心跳信息
|
||||
proxy.setVersion(machineProxyDTO.getVersion());
|
||||
proxy.setStatusCode(EnumUtils.getEnumByMessage(machineProxyDTO.getStatus(),MachineProxyStatus.class).getCode());
|
||||
return updateById(proxy);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Long> getStatusStatistics() {
|
||||
List<MachineProxy> proxyList = list();
|
||||
|
||||
if (CollectionUtils.isEmpty(proxyList)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return proxyList.stream()
|
||||
.map(proxy -> EnumUtils.getEnumByCode(proxy.getStatusCode(), MachineProxyStatus.class).getMessage())
|
||||
.collect(Collectors.groupingBy(
|
||||
Function.identity(),
|
||||
Collectors.counting() // 统计每个分组的元素数量
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateConfig(MachineProxyDTO machineProxyDTO) {
|
||||
// 参数校验
|
||||
if (machineProxyDTO == null) {
|
||||
throw new ServiceException(ServiceException.MACHINE_PROXY_DTO_NULL,"MachineProxyDTO对象为空");
|
||||
}
|
||||
|
||||
// 查询代理
|
||||
MachineProxy proxy = getById(machineProxyDTO.getId());
|
||||
|
||||
if (proxy == null) {
|
||||
throw new ServiceException(ServiceException.MACHINE_PROXY_NULL,"代理不存在");
|
||||
}
|
||||
// 更新配置
|
||||
proxy.setConfig(machineProxyDTO.getConfig());
|
||||
proxy.setUpdateDate(new Date());
|
||||
return updateById(proxy);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(List<Long> ids) {
|
||||
// 参数校验
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
throw new ServiceException(ServiceException.PARAMETER_ERROR,"参数错误");
|
||||
}
|
||||
|
||||
// 查询在线代理
|
||||
List<MachineProxy> onlineProxies = list(new LambdaQueryWrapper<MachineProxy>()
|
||||
.in(MachineProxy::getId, ids)
|
||||
.eq(MachineProxy::getStatus, MachineProxyStatus.ONLINE.getCode()));
|
||||
|
||||
if (!CollectionUtils.isEmpty(onlineProxies)) {
|
||||
List<Long> onlineIds = onlineProxies.stream()
|
||||
.map(MachineProxy::getId)
|
||||
.collect(Collectors.toList());
|
||||
throw new IllegalArgumentException("以下代理处于在线状态,无法删除: " + String.join( ",", (CharSequence) onlineIds));
|
||||
}
|
||||
|
||||
// 批量逻辑删除
|
||||
remove(new LambdaQueryWrapper<MachineProxy>()
|
||||
.in(MachineProxy::getId, ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MachineProxyDTO> list(MachineProxyDTO machineProxyDTO) {
|
||||
QueryWrapper<MachineProxy> queryWrapper = getMachineProxyQueryWrapper(machineProxyDTO);
|
||||
Page<MachineProxy> page = machineProxyMapper.selectPage(new Page<>(machineProxyDTO.getPageIndex(), machineProxyDTO.getPageSize()), queryWrapper);
|
||||
List<MachineProxyDTO> machineProxyDtos = page.getRecords().stream().map(machineProxy -> {
|
||||
MachineProxyDTO dto = new MachineProxyDTO();
|
||||
BeanUtils.copyProperties(machineProxy, dto);
|
||||
dto.setProxyType(EnumUtils.getEnumByCode(machineProxy.getStatusCode(), MachineProxyType.class).getMessage());
|
||||
dto.setStatus(EnumUtils.getEnumByCode(machineProxy.getStatusCode(), MachineProxyStatus.class).getMessage());
|
||||
return dto;
|
||||
}).toList();
|
||||
return new PageResult<>(
|
||||
page.getCurrent(),
|
||||
page.getSize(),
|
||||
page.getTotal(),
|
||||
page.getPages(),
|
||||
machineProxyDtos
|
||||
);
|
||||
}
|
||||
|
||||
private QueryWrapper<MachineProxy> getMachineProxyQueryWrapper(MachineProxyDTO machineProxyDTO){
|
||||
QueryWrapper<MachineProxy> queryWrapper = new QueryWrapper<>();
|
||||
if (machineProxyDTO.getHostIp() != null && !machineProxyDTO.getHostIp().isEmpty()) {
|
||||
queryWrapper.eq("host_ip", machineProxyDTO.getHostIp());
|
||||
}
|
||||
if (machineProxyDTO.getSshPort() != null && !machineProxyDTO.getSshPort().isEmpty()) {
|
||||
queryWrapper.eq("ssh_port", machineProxyDTO.getSshPort());
|
||||
}
|
||||
if (machineProxyDTO.getUsername() != null && !machineProxyDTO.getUsername().isEmpty()) {
|
||||
queryWrapper.eq("username", machineProxyDTO.getUsername());
|
||||
}
|
||||
if (machineProxyDTO.getDescription() != null && !machineProxyDTO.getDescription().isEmpty()) {
|
||||
queryWrapper.eq("description", machineProxyDTO.getDescription());
|
||||
}
|
||||
if (machineProxyDTO.getStatus() != null && !machineProxyDTO.getStatus().isEmpty()) {
|
||||
queryWrapper.eq("status_code", EnumUtils.getEnumByMessage(machineProxyDTO.getStatus(),MachineProxyStatus.class).getCode());
|
||||
}
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,341 @@
|
||||
package com.casic.machine.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import com.casic.machine.dto.MachineInfoDto;
|
||||
import com.casic.machine.enums.AuthenticationType;
|
||||
import com.casic.machine.enums.ConnectionStatus;
|
||||
import com.casic.machine.enums.MachineInfoStatus;
|
||||
import com.casic.machine.exception.ServiceException;
|
||||
import com.casic.machine.handler.ConnectionSession;
|
||||
import com.casic.machine.mapper.MachineInfoMapper;
|
||||
import com.casic.machine.service.MachineInfoService;
|
||||
import com.casic.machine.utils.EnumUtils;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MachineinfoServiceImpl extends ServiceImpl<MachineInfoMapper, MachineInfo> implements MachineInfoService {
|
||||
|
||||
int ENABLE=1;
|
||||
int UN_ENABLE=0;
|
||||
|
||||
@Resource
|
||||
private MachineInfoMapper machineInfoMapper;
|
||||
|
||||
/**
|
||||
* 会话ID生成器
|
||||
*/
|
||||
private final AtomicInteger sessionIdGenerator = new AtomicInteger(1000);
|
||||
|
||||
/**
|
||||
* 会话管理:会话ID -> 连接会话
|
||||
*/
|
||||
private final Map<String, ConnectionSession> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 机器名称 -> 会话ID
|
||||
*/
|
||||
private final Map<String, String> machineSessionMapping = new ConcurrentHashMap<>();
|
||||
@Override
|
||||
public boolean addMachineInfo(MachineInfoDto machineInfoDto) {
|
||||
if (machineInfoDto == null) {
|
||||
throw new ServiceException(ServiceException.MACHINE_INFO_NULL, "机器信息为空");
|
||||
}
|
||||
MachineInfo machineInfo = new MachineInfo();
|
||||
BeanUtils.copyProperties(machineInfoDto, machineInfo);
|
||||
machineInfo.setStatusCode(1);
|
||||
machineInfo.setAuthenticationTypeCode(
|
||||
"密码认证".equals(machineInfoDto.getAuthenticationType())
|
||||
? AuthenticationType.PASSWORD.getCode()
|
||||
: AuthenticationType.SECRET_KEY.getCode()
|
||||
);
|
||||
|
||||
return this.save(machineInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MachineInfoDto> listMachineInfo(MachineInfoDto machineInfoDto) {
|
||||
QueryWrapper<MachineInfo> queryWrapper = getMachineInfoQueryWrapper(machineInfoDto);
|
||||
Page<MachineInfo> page = machineInfoMapper.selectPage(
|
||||
new Page<>(machineInfoDto.getPageIndex(), machineInfoDto.getPageSize()),
|
||||
queryWrapper
|
||||
);
|
||||
|
||||
List<MachineInfoDto> machineInfoDtos = page.getRecords().stream()
|
||||
.map(machineInfo -> {
|
||||
MachineInfoDto dto = new MachineInfoDto();
|
||||
BeanUtils.copyProperties(machineInfo, dto);
|
||||
// 直接调用原有枚举转换方法
|
||||
dto.setStatus(EnumUtils.getEnumByCode(machineInfo.getStatus(), MachineInfoStatus.class).getMessage());
|
||||
dto.setAuthenticationType(EnumUtils.getEnumByCode(machineInfo.getAuthenticationType(), AuthenticationType.class).getMessage());
|
||||
return dto;
|
||||
})
|
||||
.toList();
|
||||
|
||||
return new PageResult<>(
|
||||
page.getCurrent(),
|
||||
page.getSize(),
|
||||
page.getTotal(),
|
||||
page.getPages(),
|
||||
machineInfoDtos
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateMachineInfo(MachineInfoDto machineInfoDto) {
|
||||
MachineInfo machineInfo = new MachineInfo();
|
||||
BeanUtils.copyProperties(machineInfoDto, machineInfo);
|
||||
machineInfo.setAuthenticationType(
|
||||
"密码认证".equals(machineInfoDto.getAuthenticationType())
|
||||
? AuthenticationType.PASSWORD
|
||||
: AuthenticationType.SECRET_KEY
|
||||
);
|
||||
return this.updateById(machineInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateStatus(Long machineInfoId, String status) {
|
||||
UpdateWrapper<MachineInfo> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("id", machineInfoId).set("status", status);
|
||||
return this.update(updateWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bindingSecretKey(Long machineInfoId, Long secretKeyId) {
|
||||
UpdateWrapper<MachineInfo> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("id", machineInfoId).set("secretKeyId", secretKeyId);
|
||||
return this.update(updateWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteList(List<Long> machineInfoIds) {
|
||||
machineInfoMapper.selectBatchIds(machineInfoIds).forEach(machineInfo -> {
|
||||
if (machineInfo.getStatusCode() == 1){
|
||||
this.removeById(machineInfo.getId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMachineInfo(Long machineInfoId) {
|
||||
MachineInfo machineInfo = this.getById(machineInfoId);
|
||||
if (machineInfo.getStatusCode() == 1){
|
||||
this.removeById(machineInfoId);
|
||||
}
|
||||
}
|
||||
|
||||
private QueryWrapper<MachineInfo> getMachineInfoQueryWrapper(MachineInfoDto machineInfoDto) {
|
||||
QueryWrapper<MachineInfo> queryWrapper = new QueryWrapper<>();
|
||||
if (machineInfoDto.getStatus() != null && !machineInfoDto.getStatus().isEmpty()){
|
||||
queryWrapper.eq("status_code", EnumUtils.getEnumByMessage(machineInfoDto.getStatus(), MachineInfoStatus.class).getCode());
|
||||
}
|
||||
if (machineInfoDto.getName() != null && !machineInfoDto.getName().isEmpty()) {
|
||||
queryWrapper.like("name", machineInfoDto.getName());
|
||||
}
|
||||
if (machineInfoDto.getTag() != null && !machineInfoDto.getTag().isEmpty()) {
|
||||
queryWrapper.like("tag", machineInfoDto.getTag());
|
||||
}
|
||||
if (machineInfoDto.getHostIp() != null && !machineInfoDto.getHostIp().isEmpty()) {
|
||||
queryWrapper.like("host_ip", machineInfoDto.getHostIp());
|
||||
}
|
||||
if (machineInfoDto.getDescription() != null && !machineInfoDto.getDescription().isEmpty()) {
|
||||
queryWrapper.like("description", machineInfoDto.getDescription());
|
||||
}
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean testConnection(MachineInfo machineInfo) {
|
||||
if (machineInfo.getStatus().getCode()==UN_ENABLE) {
|
||||
throw new RuntimeException("机器不可用");
|
||||
}
|
||||
log.info("测试机器连接: {}", machineInfo.getHostIp());
|
||||
|
||||
try (ConnectionSession session = createSession(machineInfo)) {
|
||||
session.connect();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("机器连接测试失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionStatus getConnectionStatus(String machineName) {
|
||||
String sessionId = machineSessionMapping.get(machineName);
|
||||
if (sessionId == null) {
|
||||
return ConnectionStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
ConnectionSession session = sessions.get(sessionId);
|
||||
return session != null ? session.getStatus() : ConnectionStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConnectionStatus> getAllConnectionStatus() {
|
||||
Map<String, ConnectionStatus> result = new HashMap<>();
|
||||
|
||||
machineSessionMapping.forEach((machineName, sessionId) -> {
|
||||
ConnectionSession session = sessions.get(sessionId);
|
||||
result.put(machineName, session != null ? session.getStatus() : ConnectionStatus.DISCONNECTED);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String connect(MachineInfo machineInfo) {
|
||||
if (machineInfo.getStatus().getCode()==UN_ENABLE) {
|
||||
throw new RuntimeException("机器不可用");
|
||||
}
|
||||
log.info("建立机器连接: {}", machineInfo.getHostIp());
|
||||
|
||||
// 检查是否已连接
|
||||
String existingSessionId = machineSessionMapping.get(machineInfo.getName());
|
||||
if (existingSessionId != null) {
|
||||
ConnectionSession existingSession = sessions.get(existingSessionId);
|
||||
if (existingSession != null && existingSession.getStatus() == ConnectionStatus.CONNECTED) {
|
||||
log.info("机器已连接,返回现有会话: {}", machineInfo.getHostIp());
|
||||
return existingSessionId;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ConnectionSession session = createSession(machineInfo);
|
||||
session.connect();
|
||||
|
||||
// 生成会话ID
|
||||
String sessionId = generateSessionId();
|
||||
|
||||
// 保存会话
|
||||
sessions.put(sessionId, session);
|
||||
machineSessionMapping.put(machineInfo.getName(), sessionId);
|
||||
|
||||
log.info("机器连接成功: {}, 会话ID: {}", machineInfo.getHostIp(), sessionId);
|
||||
return sessionId;
|
||||
} catch (Exception e) {
|
||||
log.error("机器连接失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("机器连接失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnect(String sessionId) {
|
||||
log.info("断开机器连接: {}", sessionId);
|
||||
|
||||
ConnectionSession session = sessions.get(sessionId);
|
||||
if (session == null) {
|
||||
log.warn("会话不存在: {}", sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
session.disconnect();
|
||||
|
||||
// 清理会话
|
||||
sessions.remove(sessionId);
|
||||
machineSessionMapping.entrySet().removeIf(entry -> entry.getValue().equals(sessionId));
|
||||
|
||||
log.info("机器连接已断开: {}", sessionId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("断开连接失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executeCommand(String sessionId, String command) {
|
||||
log.info("执行命令: {}, 会话ID: {}", command, sessionId);
|
||||
|
||||
ConnectionSession session = sessions.get(sessionId);
|
||||
if (session == null) {
|
||||
throw new RuntimeException("会话不存在: " + sessionId);
|
||||
}
|
||||
|
||||
if (session.getStatus() != ConnectionStatus.CONNECTED) {
|
||||
throw new RuntimeException("会话未连接: " + sessionId);
|
||||
}
|
||||
|
||||
try {
|
||||
return session.executeCommand(command);
|
||||
} catch (Exception e) {
|
||||
log.error("命令执行失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("命令执行失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath) {
|
||||
log.info("上传文件: {} -> {}, 会话ID: {}", localFilePath, remoteFilePath, sessionId);
|
||||
|
||||
ConnectionSession session = sessions.get(sessionId);
|
||||
if (session == null) {
|
||||
throw new RuntimeException("会话不存在: " + sessionId);
|
||||
}
|
||||
|
||||
if (session.getStatus() != ConnectionStatus.CONNECTED) {
|
||||
throw new RuntimeException("会话未连接: " + sessionId);
|
||||
}
|
||||
|
||||
try {
|
||||
return session.uploadFile(localFilePath, remoteFilePath);
|
||||
} catch (Exception e) {
|
||||
log.error("文件上传失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath) {
|
||||
log.info("下载文件: {} -> {}, 会话ID: {}", remoteFilePath, localFilePath, sessionId);
|
||||
|
||||
ConnectionSession session = sessions.get(sessionId);
|
||||
if (session == null) {
|
||||
throw new RuntimeException("会话不存在: " + sessionId);
|
||||
}
|
||||
|
||||
if (session.getStatus() != ConnectionStatus.CONNECTED) {
|
||||
throw new RuntimeException("会话未连接: " + sessionId);
|
||||
}
|
||||
|
||||
try {
|
||||
return session.downloadFile(remoteFilePath, localFilePath);
|
||||
} catch (Exception e) {
|
||||
log.error("文件下载失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("文件下载失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建连接会话
|
||||
*/
|
||||
private ConnectionSession createSession(MachineInfo machineInfo) {
|
||||
return new ConnectionSession(machineInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成会话ID
|
||||
*/
|
||||
private String generateSessionId() {
|
||||
return "session-" + sessionIdGenerator.incrementAndGet();
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package com.casic.machine.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
import com.casic.machine.configuration.AliYunConfig;
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import com.casic.machine.entity.SecretKey;
|
||||
import com.casic.machine.dto.SecretKeyDto;
|
||||
import com.casic.machine.exception.ServiceException;
|
||||
import com.casic.machine.mapper.SecretServiceMapper;
|
||||
import com.casic.machine.service.MachineInfoService;
|
||||
import com.casic.machine.service.SecretKeyService;
|
||||
import com.casic.machine.utils.AliOssUtil;
|
||||
import com.casic.machine.utils.PageResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Service
|
||||
public class SecretKeyServiceImpl extends ServiceImpl<SecretServiceMapper, SecretKey> implements SecretKeyService {
|
||||
@Resource
|
||||
private AliOssUtil aliOssUtil;
|
||||
|
||||
@Resource
|
||||
private MachineInfoService machineInfoService;
|
||||
|
||||
@Resource
|
||||
private AliYunConfig aliYunConfig;
|
||||
|
||||
@Resource
|
||||
private SecretServiceMapper secretServiceMapper;
|
||||
|
||||
|
||||
|
||||
//todo public方便测试,后面改为private
|
||||
public static final ExecutorService FILE_DELETE_EXECUTOR = Executors.newFixedThreadPool(10);
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(SecretKeyServiceImpl.class);
|
||||
|
||||
@Override
|
||||
public boolean addSecretKey(SecretKeyDto secretKeyDto, MultipartFile file) {
|
||||
secretKeyDto.setFile(file);
|
||||
String fileName = aliOssUtil.save(file);
|
||||
String path = "https://" + aliYunConfig.getBucketName() + "." + aliYunConfig.getEndpoint() + "/" + fileName;
|
||||
secretKeyDto.setPath(path);
|
||||
secretKeyDto.setFileName(fileName);
|
||||
SecretKey secretKey = new SecretKey();
|
||||
BeanUtils.copyProperties(secretKeyDto,secretKey);
|
||||
return this.save(secretKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindingMachine(Long secretKeyId, List<Long> machineInfoIds) {
|
||||
List<MachineInfo> machineInfos = machineInfoService.listByIds(machineInfoIds);
|
||||
machineInfos.forEach(machineInfo -> machineInfo.setSecretKeyId(secretKeyId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSecretKey(SecretKeyDto secretKeyDto, MultipartFile file) {
|
||||
if (file != null){
|
||||
secretKeyDto.setFile(file);
|
||||
String fileName = aliOssUtil.save(file);
|
||||
String path = "https://" + aliYunConfig.getBucketName() + "." + aliYunConfig.getEndpoint() + "/" + fileName;
|
||||
secretKeyDto.setPath(path);
|
||||
secretKeyDto.setFileName(fileName);
|
||||
}
|
||||
SecretKey secretKey = new SecretKey();
|
||||
BeanUtils.copyProperties(secretKeyDto,secretKey);
|
||||
return this.updateById(secretKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteSecretKey(Long secretKeyId) {
|
||||
String fileName = this.getById(secretKeyId).getFileName();
|
||||
aliOssUtil.deleteFile(fileName);
|
||||
return this.removeById(secretKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<SecretKey> listSecretKey(SecretKeyDto secretKeyDto) {
|
||||
QueryWrapper<SecretKey> queryWrapper = new QueryWrapper<>();
|
||||
if (secretKeyDto.getName() != null && !secretKeyDto.getName().isEmpty()){
|
||||
queryWrapper.like("name", secretKeyDto.getName());
|
||||
}
|
||||
if (secretKeyDto.getDescription() != null && !secretKeyDto.getDescription().isEmpty()){
|
||||
queryWrapper.like("description", secretKeyDto.getDescription());
|
||||
}
|
||||
Page<SecretKey> page = secretServiceMapper.selectPage(new Page<>(secretKeyDto.getPageIndex(), secretKeyDto.getPageSize()), queryWrapper);
|
||||
return new PageResult<>(
|
||||
page.getCurrent(),
|
||||
page.getSize(),
|
||||
page.getTotal(),
|
||||
page.getPages(),
|
||||
page.getRecords()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<InputStreamResource> downloadSecretKeyFile(Long secretKeyId) {
|
||||
String fileName = this.getById(secretKeyId).getFileName();
|
||||
return aliOssUtil.downloadFile(fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteList(List<Long> secretKeyIds) {
|
||||
List<SecretKey> secretKeys = this.listByIds(secretKeyIds);
|
||||
// 提交异步任务到线程池
|
||||
FILE_DELETE_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
for (SecretKey secretKey : secretKeys) {
|
||||
if (secretKey.getFileName() != null && !secretKey.getFileName().isEmpty()){
|
||||
aliOssUtil.deleteFile(secretKey.getFileName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("异步删除文件失败:{}", e.getMessage());
|
||||
throw new ServiceException(ServiceException.DELETE_FILE_FAIL,"异步删除文件失败:"+e.getMessage());
|
||||
}
|
||||
});
|
||||
return secretServiceMapper.deleteBatchIds(secretKeyIds) > 0 ;
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.casic.machine.utils;
|
||||
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.model.OSSObject;
|
||||
import com.casic.machine.configuration.AliYunConfig;
|
||||
import com.casic.machine.exception.ServiceException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
|
||||
@Component
|
||||
public class AliOssUtil{
|
||||
|
||||
@Autowired
|
||||
private OSS ossClient;
|
||||
|
||||
@Resource
|
||||
private AliYunConfig aliyunConfig;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AliOssUtil.class);
|
||||
|
||||
public String save(MultipartFile file) {
|
||||
try {
|
||||
String fileName = generateUniqueFileName(file.getName());
|
||||
ossClient.putObject(
|
||||
// 存储桶名称
|
||||
aliyunConfig.getBucketName(),
|
||||
//对象键(Object Key),即文件在 OSS 中的完整路径和名称
|
||||
fileName,
|
||||
//文件内容的输入流,用于读取待上传的文件数据。
|
||||
file.getInputStream(),
|
||||
//文件的元数据信息,如内容类型(Content-Type)、缓存策略、文件大小等。
|
||||
null
|
||||
);
|
||||
return fileName;
|
||||
} catch (Exception e) {
|
||||
logger.info("文件上传失败:{}", e.getMessage());
|
||||
throw new ServiceException(ServiceException.UPLOADING_FILE_FAIL, "上传文件失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public ResponseEntity<InputStreamResource> downloadFile(String fileName) {
|
||||
try {
|
||||
// 读取文件流
|
||||
InputStream inputStream = read(fileName);
|
||||
|
||||
// 处理文件名编码(防止中文乱码)
|
||||
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
|
||||
.replaceAll("\\+", "%20");
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodedFileName);
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
headers.setCacheControl("no-cache, no-store, must-revalidate");
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(new InputStreamResource(inputStream));
|
||||
} catch (Exception e) {
|
||||
logger.error("下载失败:{}", e.getMessage());
|
||||
throw new ServiceException(ServiceException.DOWNLOAD_FILE_FAIL, "下载失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteFile(String fileName) {
|
||||
try {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
throw new ServiceException(ServiceException.FILENAME_NULL, "文件名不能为空");
|
||||
}
|
||||
|
||||
// 调用 OSS 客户端删除文件
|
||||
ossClient.deleteObject(aliyunConfig.getBucketName(), fileName);
|
||||
logger.info("文件删除成功: {}", fileName);
|
||||
} catch (Exception e) {
|
||||
logger.error("删除文件失败: {}", e.getMessage());
|
||||
throw new ServiceException(ServiceException.DELETE_FILE_FAIL, "删除文件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream read(String fileName) {
|
||||
try {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
throw new ServiceException(ServiceException.FILENAME_NULL, "文件名不能为空");
|
||||
}
|
||||
OSSObject ossObject = ossClient.getObject(aliyunConfig.getBucketName(), fileName);
|
||||
return ossObject.getObjectContent();
|
||||
} catch (Exception e) {
|
||||
logger.error("读取文件失败:{}", e.getMessage());
|
||||
throw new ServiceException(ServiceException.READ_FILE_FAIL, "读取文件失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 生成带时间戳的唯一文件名
|
||||
private String generateUniqueFileName(String originalFileName) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String ext = originalFileName.substring(originalFileName.lastIndexOf("."));
|
||||
return timestamp + ext;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.casic.machine.utils;
|
||||
|
||||
|
||||
import com.casic.machine.enums.CodeEnum;
|
||||
|
||||
public class EnumUtils {
|
||||
/**
|
||||
* 根据code和枚举类型获取对应的枚举值
|
||||
*
|
||||
* @param code 枚举的code值
|
||||
* @param enumClass 实现了CodeEnum接口的枚举类
|
||||
* @param <T> 枚举类型
|
||||
* @return 对应的枚举值,若未找到则返回null
|
||||
*/
|
||||
public static <T extends CodeEnum> T getEnumByCode(Object code, Class<T> enumClass) {
|
||||
if (code == null || enumClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 遍历枚举值查找匹配的code
|
||||
for (T enumConstant : enumClass.getEnumConstants()) {
|
||||
if (code.equals(enumConstant.getCode())) {
|
||||
return enumConstant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据message和枚举类型获取对应的枚举值
|
||||
*
|
||||
* @param message 枚举的message值
|
||||
* @param enumClass 实现了CodeEnum接口的枚举类
|
||||
* @param <T> 枚举类型
|
||||
* @return 对应的枚举值,若未找到则返回null
|
||||
*/
|
||||
public static <T extends CodeEnum> T getEnumByMessage(String message, Class<T> enumClass) {
|
||||
if (message == null || enumClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 遍历枚举值查找匹配的message
|
||||
for (T enumConstant : enumClass.getEnumConstants()) {
|
||||
if (message.equals(enumConstant.getMessage())) {
|
||||
return enumConstant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.casic.machine.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果通用类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PageResult<T> implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private Long pageNum;
|
||||
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
private Long pageSize;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
private Long pages;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
private List<T> list;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.casic.machine.utils;
|
||||
|
||||
import io.micrometer.common.util.StringUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 属性操作工具类
|
||||
*/
|
||||
public class PropertyUtils {
|
||||
|
||||
/**
|
||||
* 如果不为空则设置值
|
||||
*
|
||||
* @param value 值
|
||||
* @param setter 具体set操作
|
||||
*/
|
||||
public static void setIfNotBlank(String value, Consumer<String> setter) {
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
setter.accept(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果不为空则设置值
|
||||
*
|
||||
* @param value 值
|
||||
* @param setter 具体set操作
|
||||
*/
|
||||
public static <T> void setIfNotNull(T value, Consumer<T> setter) {
|
||||
if (value != null) {
|
||||
setter.accept(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
spring:
|
||||
application:
|
||||
name: machine-management-module
|
||||
|
||||
# datasource:
|
||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://localhost:3306/resource_management?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
|
||||
# username:
|
||||
# password:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/resource_management?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
|
||||
username: root
|
||||
password: 031219
|
@ -0,0 +1,37 @@
|
||||
spring:
|
||||
application:
|
||||
name: machine-management-module
|
||||
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:/mapper/*.xml
|
||||
type-aliases-package: com.casic.entity
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
map-underscore-to-camel-case: true
|
||||
|
||||
logging:
|
||||
pattern:
|
||||
console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
|
||||
|
||||
server:
|
||||
port: 10111
|
||||
|
||||
#SpringDoc访问路径
|
||||
springdoc.swagger-ui.path: /api-docs.html
|
||||
|
||||
|
||||
aliyun:
|
||||
oss:
|
||||
# OSS 服务的访问域名
|
||||
endpoint: "zyj031219.xin"
|
||||
# 访问密钥 ID
|
||||
accessKeyId: "LTAI5tKnFb95ytubCbYfmf5g"
|
||||
# 访问密钥 Secret
|
||||
accessKeySecret: "EDtyMKuu5MtViiMoDgAYkWL0JV34AF"
|
||||
# 存储空间名称
|
||||
bucketName: "zyjtest031219"
|
||||
|
||||
|
@ -0,0 +1,184 @@
|
||||
package com.casic.machine.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.casic.commons.exception.ServiceException;
|
||||
import com.casic.commons.utils.PageResult;
|
||||
import com.casic.machine.dto.MachineEnvDTO;
|
||||
import com.casic.machine.entity.MachineEnv;
|
||||
import com.casic.machine.mapper.MachineEnvMapper;
|
||||
import com.casic.machine.service.MachineEnvService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
@Rollback(value = true) // 测试后回滚数据
|
||||
@Sql(scripts = {"classpath:sql/machine_env_test_data.sql"}) // 初始化测试数据(可选)
|
||||
public class MachineEnvServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private MachineEnvService machineEnvService;
|
||||
|
||||
@Autowired
|
||||
private MachineEnvMapper machineEnvMapper;
|
||||
|
||||
private MachineEnvDTO validDto;
|
||||
private MachineEnvDTO invalidKeyDto;
|
||||
private Long existingMachineId;
|
||||
private Long nonExistingMachineId;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// 准备测试数据
|
||||
existingMachineId = 1L; // 假设数据库中存在ID为1的机器环境变量
|
||||
nonExistingMachineId = 999L; // 不存在的机器ID
|
||||
|
||||
// 有效测试数据
|
||||
validDto = new MachineEnvDTO();
|
||||
validDto.setMachineInfoId(existingMachineId);
|
||||
validDto.setEnvKey("TEST_ENV_KEY");
|
||||
validDto.setEnvValue("test-value");
|
||||
validDto.setSensitive(true);
|
||||
|
||||
// 无效Key测试数据(包含非法字符)
|
||||
invalidKeyDto = new MachineEnvDTO();
|
||||
invalidKeyDto.setMachineInfoId(existingMachineId);
|
||||
invalidKeyDto.setEnvKey("test-env-key"); // 包含'-',不符合正则
|
||||
invalidKeyDto.setEnvValue("test-value");
|
||||
}
|
||||
|
||||
// ==================== 更新环境变量测试 ====================
|
||||
@Test
|
||||
void testUpdateEnv_ValidData_ShouldSucceed() {
|
||||
// 执行更新(假设数据库中已存在machineId=1的记录)
|
||||
boolean result = machineEnvService.update(validDto);
|
||||
|
||||
// 验证结果
|
||||
assertTrue(result);
|
||||
|
||||
// 检查数据库数据是否更新
|
||||
MachineEnv updatedEnv = machineEnvMapper.selectOne(
|
||||
new LambdaQueryWrapper<MachineEnv>().eq(MachineEnv::getMachineId, existingMachineId)
|
||||
);
|
||||
assertNotNull(updatedEnv);
|
||||
assertEquals(validDto.getEnvKey(), updatedEnv.getEnvKey());
|
||||
assertEquals(validDto.getEnvValue(), updatedEnv.getEnvValue());
|
||||
assertEquals(validDto.getSensitive(), updatedEnv.getSensitive());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateEnv_NullDto_ShouldThrowException() {
|
||||
|
||||
MachineEnvDTO machineEnvDTO = new MachineEnvDTO();
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
machineEnvService.update(machineEnvDTO);
|
||||
}, "环境变量不能为空");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateEnv_InvalidKey_ShouldThrowException() {
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
machineEnvService.update(invalidKeyDto);
|
||||
}, "环境变量键不合法");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateEnv_SensitiveKey_ShouldMarkAsSensitive() {
|
||||
MachineEnvDTO sensitiveDto = new MachineEnvDTO();
|
||||
sensitiveDto.setMachineInfoId(existingMachineId);
|
||||
sensitiveDto.setEnvKey("DB_PASSWORD"); // 包含敏感词
|
||||
sensitiveDto.setEnvValue("secret");
|
||||
|
||||
machineEnvService.update(sensitiveDto);
|
||||
|
||||
MachineEnv env = machineEnvMapper.selectOne(
|
||||
new LambdaQueryWrapper<MachineEnv>().eq(MachineEnv::getMachineId, existingMachineId)
|
||||
);
|
||||
assertTrue(env.getSensitive());
|
||||
}
|
||||
|
||||
// ==================== 删除环境变量测试 ====================
|
||||
@Test
|
||||
void testDeleteByMachineId_ExistingId_ShouldSucceed() {
|
||||
machineEnvService.deleteByMachineId(existingMachineId);
|
||||
MachineEnv env = machineEnvMapper.selectById(existingMachineId);
|
||||
assertNull(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteByMachineId_NonExistingId_ShouldDoNothing() {
|
||||
machineEnvService.deleteByMachineId(nonExistingMachineId);
|
||||
// 不抛出异常,静默处理
|
||||
}
|
||||
|
||||
// ==================== 根据机器ID查询测试 ====================
|
||||
@Test
|
||||
void testGetByMachineId_ExistingId_ShouldReturnDto() {
|
||||
MachineEnvDTO dto = machineEnvService.getByMachineId(existingMachineId);
|
||||
assertNotNull(dto);
|
||||
assertEquals(existingMachineId, dto.getMachineInfoId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetByMachineId_NonExistingId_ShouldReturnNull() {
|
||||
MachineEnvDTO dto = machineEnvService.getByMachineId(nonExistingMachineId);
|
||||
assertNull(dto);
|
||||
}
|
||||
|
||||
// ==================== 列表查询测试 ====================
|
||||
@Test
|
||||
void testListEnv_Pagination_ShouldReturnValidPage() {
|
||||
MachineEnvDTO queryDto = new MachineEnvDTO();
|
||||
queryDto.setPageIndex(1);
|
||||
queryDto.setPageSize(10);
|
||||
queryDto.setEnvKey("TEST"); // 假设测试数据中存在包含"TEST"的键
|
||||
|
||||
PageResult<MachineEnvDTO> pageResult = machineEnvService.listEnv(queryDto);
|
||||
|
||||
assertNotNull(pageResult.getList());
|
||||
assertTrue(pageResult.getTotal() >= 0);
|
||||
assertEquals(1, pageResult.getPageNum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testListEnv_SortByCreateTime_ShouldBeOrdered() {
|
||||
MachineEnvDTO queryDto = new MachineEnvDTO();
|
||||
queryDto.setSortField("createTime");
|
||||
queryDto.setSortDirection("desc");
|
||||
|
||||
PageResult<MachineEnvDTO> pageResult = machineEnvService.listEnv(queryDto);
|
||||
|
||||
List<MachineEnvDTO> list = pageResult.getList();
|
||||
if (!list.isEmpty()) {
|
||||
Date prevDate = list.get(0).getCreateDate();
|
||||
for (int i = 1; i < list.size(); i++) {
|
||||
Date currDate = list.get(i).getCreateDate();
|
||||
assertTrue(currDate.before(prevDate) || currDate.equals(prevDate), "排序应为降序");
|
||||
prevDate = currDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 批量删除测试 ====================
|
||||
@Test
|
||||
void testDeleteList_ValidIds_ShouldDeleteBatch() {
|
||||
// 假设测试数据中有ID为1和2的记录
|
||||
List<Long> ids = List.of(1L, 2L);
|
||||
machineEnvService.deleteList(ids);
|
||||
|
||||
long count = machineEnvMapper.selectCount(new LambdaQueryWrapper<MachineEnv>().in(MachineEnv::getId, ids));
|
||||
assertEquals(0, count);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
package com.casic.machine.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.casic.commons.exception.ServiceException;
|
||||
import com.casic.commons.utils.EnumUtils;
|
||||
import com.casic.commons.utils.PageResult;
|
||||
import com.casic.machine.dto.MachineProxyDTO;
|
||||
import com.casic.machine.entity.MachineProxy;
|
||||
import com.casic.machine.enums.MachineProxyStatus;
|
||||
import com.casic.machine.enums.MachineProxyType;
|
||||
import com.casic.machine.mapper.MachineProxyMapper;
|
||||
import com.casic.machine.service.MachineProxyService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
@Rollback(true)
|
||||
@Sql(scripts = {"classpath:sql/machine_proxy_test_data.sql"})
|
||||
public class MachineProxyServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private MachineProxyService machineProxyService;
|
||||
|
||||
@Autowired
|
||||
private MachineProxyMapper machineProxyMapper;
|
||||
|
||||
private MachineProxyDTO validProxyDTO;
|
||||
private Long existingProxyId;
|
||||
private Long nonExistingProxyId;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// 初始化测试数据(假设 SQL 脚本已插入一条状态为 OFFLINE 的代理)
|
||||
existingProxyId = 1L;
|
||||
nonExistingProxyId = 999L;
|
||||
|
||||
// 有效代理 DTO
|
||||
validProxyDTO = new MachineProxyDTO();
|
||||
validProxyDTO.setProxyType(MachineProxyType.SOCKS5.getMessage());
|
||||
validProxyDTO.setHostIp("192.168.1.100");
|
||||
validProxyDTO.setSshPort("22");
|
||||
validProxyDTO.setUsername("test_user");
|
||||
validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
|
||||
}
|
||||
|
||||
// ============================== 注册代理测试 ==============================
|
||||
@Test
|
||||
void testRegister_ValidData_ShouldSucceed() {
|
||||
// 执行注册
|
||||
boolean result = machineProxyService.register(validProxyDTO);
|
||||
assertTrue(result, "注册失败");
|
||||
|
||||
// 使用 Lambda 表达式查询(推荐)
|
||||
MachineProxy proxy = machineProxyMapper.selectOne(
|
||||
new LambdaQueryWrapper<MachineProxy>()
|
||||
.eq(MachineProxy::getHostIp, validProxyDTO.getHostIp())
|
||||
);
|
||||
|
||||
// 断言数据存在
|
||||
assertNotNull(proxy, "代理记录未写入数据库");
|
||||
assertEquals(MachineProxyType.SOCKS5.getCode(), proxy.getProxyTypeCode());
|
||||
assertEquals(MachineProxyStatus.INSTALLING.getCode(), proxy.getStatusCode());
|
||||
assertEquals(validProxyDTO.getHostIp(), proxy.getHostIp(), "IP 地址不一致");
|
||||
}
|
||||
|
||||
// ============================== 更新状态测试 ==============================
|
||||
@Test
|
||||
void testUpdateStatus_ExistingProxy_ShouldUpdateStatus() {
|
||||
// 准备数据:查询现有代理(状态为 OFFLINE)
|
||||
MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertEquals(MachineProxyStatus.OFFLINE.getCode(), proxy.getStatusCode());
|
||||
|
||||
// 执行状态更新为 ONLINE
|
||||
validProxyDTO.setId(existingProxyId);
|
||||
validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
|
||||
boolean result = machineProxyService.updateStatus(validProxyDTO);
|
||||
|
||||
// 验证结果
|
||||
assertTrue(result);
|
||||
proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertEquals(MachineProxyStatus.ONLINE.getCode(), proxy.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateStatus_NullDto_ShouldThrowException() {
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
machineProxyService.updateStatus(null);
|
||||
}, "MachineProxyDTO对象为空");
|
||||
}
|
||||
|
||||
// ============================== 心跳测试 ==============================
|
||||
@Test
|
||||
void testHeartbeat_ValidData_ShouldUpdateVersionAndStatus() {
|
||||
// 准备数据:现有代理状态为 OFFLINE,版本为 1.0.0
|
||||
MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertEquals("1.0.0", proxy.getVersion());
|
||||
assertEquals(MachineProxyStatus.OFFLINE.getCode(), proxy.getStatusCode());
|
||||
|
||||
// 发送心跳,更新版本和状态
|
||||
validProxyDTO.setId(existingProxyId);
|
||||
validProxyDTO.setVersion("2.0.0");
|
||||
validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
|
||||
machineProxyService.heartbeat(validProxyDTO);
|
||||
|
||||
// 验证结果
|
||||
proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertEquals("2.0.0", proxy.getVersion());
|
||||
assertEquals(MachineProxyStatus.ONLINE.getCode(), proxy.getStatusCode());
|
||||
}
|
||||
|
||||
// ============================== 状态统计测试 ==============================
|
||||
@Test
|
||||
void testGetStatusStatistics_ShouldReturnValidCounts() {
|
||||
// 假设测试数据中有 OFFLINE(1)、INSTALLING(1)、ONLINE(1) 三种状态
|
||||
Map<String, Long> stats = machineProxyService.getStatusStatistics();
|
||||
|
||||
// 验证统计结果
|
||||
assertEquals(3, stats.size());
|
||||
assertTrue(stats.containsKey(MachineProxyStatus.OFFLINE.getMessage()));
|
||||
assertTrue(stats.containsKey(MachineProxyStatus.INSTALLING.getMessage()));
|
||||
assertTrue(stats.containsKey(MachineProxyStatus.ONLINE.getMessage()));
|
||||
assertEquals(1L, stats.get(MachineProxyStatus.OFFLINE.getMessage()));
|
||||
}
|
||||
|
||||
// ============================== 更新配置测试 ==============================
|
||||
@Test
|
||||
void testUpdateConfig_ValidConfig_ShouldSucceed() {
|
||||
// 准备数据:现有代理配置为空
|
||||
MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertNull(proxy.getConfig());
|
||||
|
||||
// 更新配置
|
||||
validProxyDTO.setId(existingProxyId);
|
||||
validProxyDTO.setConfig("{\"port\": 8080}");
|
||||
boolean result = machineProxyService.updateConfig(validProxyDTO);
|
||||
|
||||
// 验证结果
|
||||
assertTrue(result);
|
||||
proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertEquals("{\"port\": 8080}", proxy.getConfig());
|
||||
}
|
||||
|
||||
// ============================== 删除代理测试 ==============================
|
||||
@Test
|
||||
void testDelete_OfflineProxy_ShouldDeleteSuccessfully() {
|
||||
// 准备数据:状态为 OFFLINE 的代理 ID
|
||||
List<Long> ids = Collections.singletonList(existingProxyId);
|
||||
machineProxyService.delete(ids);
|
||||
|
||||
// 验证删除
|
||||
MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
assertNull(proxy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDelete_OnlineProxy_ShouldThrowException() {
|
||||
// 先将代理状态改为 ONLINE
|
||||
MachineProxy onlineProxy = new MachineProxy();
|
||||
onlineProxy.setId(existingProxyId);
|
||||
onlineProxy.setStatusCode(MachineProxyStatus.ONLINE.getCode());
|
||||
machineProxyMapper.updateById(onlineProxy);
|
||||
|
||||
// 执行删除
|
||||
List<Long> ids = Collections.singletonList(existingProxyId);
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
machineProxyService.delete(ids);
|
||||
}, "以下代理处于在线状态,无法删除: 1");
|
||||
}
|
||||
|
||||
// ============================== 列表查询测试 ==============================
|
||||
@Test
|
||||
void testList_WithStatusFilter_ShouldReturnMatchedRecords() {
|
||||
// 查询状态为 OFFLINE 的代理
|
||||
MachineProxyDTO queryDto = new MachineProxyDTO();
|
||||
queryDto.setStatus(MachineProxyStatus.OFFLINE.getMessage());
|
||||
|
||||
PageResult<MachineProxyDTO> pageResult = machineProxyService.list(queryDto);
|
||||
|
||||
// 验证结果
|
||||
assertFalse(pageResult.getList().isEmpty());
|
||||
pageResult.getList().forEach(dto ->
|
||||
assertEquals(MachineProxyStatus.OFFLINE.getMessage(), dto.getStatus())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testList_WithHostIpFilter_ShouldReturnMatchedRecords() {
|
||||
// 假设测试数据中存在 host_ip 为 "192.168.1.1" 的代理
|
||||
MachineProxyDTO queryDto = new MachineProxyDTO();
|
||||
queryDto.setHostIp("192.168.1.1");
|
||||
|
||||
PageResult<MachineProxyDTO> pageResult = machineProxyService.list(queryDto);
|
||||
|
||||
// 验证结果
|
||||
assertFalse(pageResult.getList().isEmpty());
|
||||
pageResult.getList().forEach(dto ->
|
||||
assertEquals("192.168.1.1", dto.getHostIp())
|
||||
);
|
||||
}
|
||||
|
||||
// ============================== 辅助方法测试 ==============================
|
||||
@Test
|
||||
void testEnumUtils_ConvertCodeToMessage() {
|
||||
// 验证代理类型枚举转换
|
||||
String typeMessage = EnumUtils.getEnumByCode(MachineProxyType.HTTP.getCode(), MachineProxyType.class).getMessage();
|
||||
assertEquals("HTTP", typeMessage);
|
||||
|
||||
// 验证状态枚举转换
|
||||
String statusMessage = EnumUtils.getEnumByCode(MachineProxyStatus.INSTALLING.getCode(), MachineProxyStatus.class).getMessage();
|
||||
assertEquals("安装中", statusMessage);
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package com.casic.machine.service.impl;
|
||||
|
||||
import com.casic.commons.exception.ServiceException;
|
||||
import com.casic.machine.dto.MachineInfoDto;
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import com.casic.machine.enums.AuthenticationType;
|
||||
import com.casic.machine.enums.ConnectionStatus;
|
||||
import com.casic.machine.enums.MachineInfoStatus;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
@Rollback(true)
|
||||
@Sql(scripts = {"classpath:sql/machine_info_test_data.sql"})
|
||||
public class MachineinfoServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private MachineinfoServiceImpl machineInfoService;
|
||||
|
||||
private MachineInfoDto validMachineInfoDto;
|
||||
private Long existingMachineId;
|
||||
private Long nonExistingMachineId;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// 初始化测试数据(假设 SQL 脚本已插入一条状态为 ENABLE 的机器)
|
||||
existingMachineId = 1L;
|
||||
nonExistingMachineId = 999L;
|
||||
|
||||
// 有效机器信息 DTO
|
||||
validMachineInfoDto = new MachineInfoDto();
|
||||
validMachineInfoDto.setName("Test Machine");
|
||||
validMachineInfoDto.setHostIp("192.168.1.101");
|
||||
validMachineInfoDto.setSshPort("22");
|
||||
validMachineInfoDto.setUsername("testuser");
|
||||
validMachineInfoDto.setAuthenticationType(AuthenticationType.PASSWORD.getMessage());
|
||||
validMachineInfoDto.setStatus(MachineInfoStatus.ENABLE.getMessage());
|
||||
}
|
||||
|
||||
// ======================= 新增机器测试 =======================
|
||||
@Test
|
||||
void testAddMachineInfo_ValidData_ShouldSucceed() {
|
||||
boolean result = machineInfoService.addMachineInfo(validMachineInfoDto);
|
||||
assertTrue(result);
|
||||
|
||||
// 验证数据库存在记录
|
||||
MachineInfo machineInfo = machineInfoService.getById(validMachineInfoDto.getId());
|
||||
assertNotNull(machineInfo);
|
||||
assertEquals(validMachineInfoDto.getHostIp(), machineInfo.getHostIp());
|
||||
assertEquals(AuthenticationType.PASSWORD.getCode(), machineInfo.getAuthenticationTypeCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMachineInfo_NullDto_ShouldThrowException() {
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
machineInfoService.addMachineInfo(null);
|
||||
}, "机器信息为空");
|
||||
}
|
||||
|
||||
// ======================= 列表查询测试 =======================
|
||||
@Test
|
||||
void testListMachineInfo_WithStatusFilter_ShouldReturnValidRecords() {
|
||||
MachineInfoDto queryDto = new MachineInfoDto();
|
||||
queryDto.setStatus(MachineInfoStatus.ENABLE.getMessage());
|
||||
|
||||
var pageResult = machineInfoService.listMachineInfo(queryDto);
|
||||
assertFalse(pageResult.getList().isEmpty());
|
||||
pageResult.getList().forEach(dto ->
|
||||
assertEquals(MachineInfoStatus.ENABLE.getMessage(), dto.getStatus())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testListMachineInfo_WithHostIpFilter_ShouldFilterRecords() {
|
||||
MachineInfoDto queryDto = new MachineInfoDto();
|
||||
queryDto.setHostIp("192.168.1.100"); // 假设测试数据中的 IP
|
||||
|
||||
var pageResult = machineInfoService.listMachineInfo(queryDto);
|
||||
assertFalse(pageResult.getList().isEmpty());
|
||||
pageResult.getList().forEach(dto ->
|
||||
assertTrue(dto.getHostIp().contains("192.168.1.100"))
|
||||
);
|
||||
}
|
||||
|
||||
// ======================= 更新机器状态测试 =======================
|
||||
@Test
|
||||
void testUpdateStatus_ValidId_ShouldUpdateStatus() {
|
||||
boolean result = machineInfoService.updateStatus(existingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
|
||||
assertTrue(result);
|
||||
|
||||
MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
assertEquals(MachineInfoStatus.UN_ENABLE.getCode(), machineInfo.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateStatus_NonExistingId_ShouldFail() {
|
||||
boolean result = machineInfoService.updateStatus(nonExistingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
// ======================= 连接测试 =======================
|
||||
@Test
|
||||
void testTestConnection_EnabledMachine_ShouldSucceed() {
|
||||
MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
assertTrue(machineInfoService.testConnection(machineInfo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTestConnection_DisabledMachine_ShouldThrowException() {
|
||||
// 先禁用机器
|
||||
machineInfoService.updateStatus(existingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
|
||||
MachineInfo disabledMachine = machineInfoService.getById(existingMachineId);
|
||||
|
||||
assertThrows(RuntimeException.class, () -> {
|
||||
machineInfoService.testConnection(disabledMachine);
|
||||
}, "机器不可用");
|
||||
}
|
||||
|
||||
// ======================= 会话管理测试 =======================
|
||||
@Test
|
||||
void testConnect_NewMachine_ShouldCreateSession() {
|
||||
MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
String sessionId = machineInfoService.connect(machineInfo);
|
||||
|
||||
assertNotNull(sessionId);
|
||||
assertTrue(sessionId.startsWith("session-"));
|
||||
assertEquals(ConnectionStatus.CONNECTING, machineInfoService.getConnectionStatus(machineInfo.getName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================= 删除机器测试 =======================
|
||||
@Test
|
||||
void testDeleteMachineInfo_EnabledMachine_ShouldDelete() {
|
||||
machineInfoService.deleteMachineInfo(existingMachineId);
|
||||
assertNull(machineInfoService.getById(existingMachineId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteList_ValidIds_ShouldDeleteBatch() {
|
||||
machineInfoService.deleteList(Collections.singletonList(existingMachineId));
|
||||
var list = machineInfoService.list();
|
||||
assertFalse(list.contains(existingMachineId));
|
||||
}
|
||||
|
||||
// ======================= 辅助功能测试 =======================
|
||||
@Test
|
||||
void testAuthenticationTypeConversion() {
|
||||
validMachineInfoDto.setAuthenticationType(AuthenticationType.SECRET_KEY.getMessage());
|
||||
MachineInfo machineInfo = new MachineInfo();
|
||||
BeanUtils.copyProperties(validMachineInfoDto, machineInfo);
|
||||
|
||||
assertEquals(AuthenticationType.SECRET_KEY.getCode(), machineInfo.getAuthenticationTypeCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAllConnectionStatus_ShouldReturnStatusMap() {
|
||||
MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
machineInfoService.connect(machineInfo);
|
||||
|
||||
var statusMap = machineInfoService.getAllConnectionStatus();
|
||||
assertFalse(statusMap.isEmpty());
|
||||
assertTrue(statusMap.containsValue(ConnectionStatus.CONNECTING));
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package com.casic.machine.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.casic.commons.exception.ServiceException;
|
||||
import com.casic.commons.utils.AliOssUtil;
|
||||
import com.casic.machine.entity.MachineInfo;
|
||||
import com.casic.machine.entity.SecretKey;
|
||||
import com.casic.machine.dto.SecretKeyDto;
|
||||
import com.casic.machine.mapper.SecretServiceMapper;
|
||||
import com.casic.machine.service.MachineInfoService;
|
||||
import com.jayway.jsonpath.internal.Utils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SecretKeyServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private SecretKeyServiceImpl secretKeyService;
|
||||
|
||||
@Mock
|
||||
private AliOssUtil aliOssUtil;
|
||||
|
||||
@Mock
|
||||
private SecretServiceMapper secretServiceMapper;
|
||||
|
||||
@Mock
|
||||
private MachineInfoService machineInfoService;
|
||||
|
||||
private SecretKeyDto validSecretKeyDto;
|
||||
private MockMultipartFile mockFile;
|
||||
private final Long TEST_SECRET_KEY_ID = 1L;
|
||||
private final String TEST_FILE_NAME = "test_key.pem";
|
||||
private final String TEST_PATH = "https://bucket.endpoint/test_key.pem";
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws IOException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
validSecretKeyDto = new SecretKeyDto();
|
||||
validSecretKeyDto.setName("Test Key");
|
||||
validSecretKeyDto.setDescription("Test secret key");
|
||||
|
||||
// 创建模拟文件
|
||||
byte[] fileContent = "test content".getBytes();
|
||||
mockFile = new MockMultipartFile(
|
||||
"file",
|
||||
TEST_FILE_NAME,
|
||||
"application/octet-stream",
|
||||
new ByteArrayInputStream(fileContent)
|
||||
);
|
||||
|
||||
// 模拟 OSS 工具返回值
|
||||
when(aliOssUtil.save(any(MockMultipartFile.class))).thenReturn(TEST_FILE_NAME);
|
||||
|
||||
}
|
||||
|
||||
// ======================= 新增密钥测试 =======================
|
||||
@Test
|
||||
void testAddSecretKey_ValidData_ShouldSucceed() throws IOException {
|
||||
// 执行新增
|
||||
boolean result = secretKeyService.addSecretKey(validSecretKeyDto, mockFile);
|
||||
|
||||
// 验证 OSS 保存调用
|
||||
verify(aliOssUtil, times(1)).save(mockFile);
|
||||
|
||||
// 验证实体属性
|
||||
SecretKey savedKey = new SecretKey();
|
||||
BeanUtils.copyProperties(validSecretKeyDto, savedKey);
|
||||
savedKey.setFileName(TEST_FILE_NAME);
|
||||
savedKey.setPath(TEST_PATH);
|
||||
|
||||
// 验证 Mapper 调用
|
||||
verify(secretServiceMapper, times(1)).insert(savedKey);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddSecretKey_NullFile_ShouldThrowException() {
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
secretKeyService.addSecretKey(validSecretKeyDto, null);
|
||||
}, "文件为空");
|
||||
}
|
||||
|
||||
// ======================= 绑定机器测试 =======================
|
||||
@Test
|
||||
void testBindingMachine_ValidIds_ShouldUpdateMachine() {
|
||||
// 模拟机器列表
|
||||
List<Long> machineIds = Collections.singletonList(1L);
|
||||
when(machineInfoService.listByIds(machineIds)).thenReturn(Collections.singletonList(new MachineInfo()));
|
||||
|
||||
secretKeyService.bindingMachine(TEST_SECRET_KEY_ID, machineIds);
|
||||
|
||||
// 验证机器信息更新
|
||||
verify(machineInfoService, times(1)).listByIds(machineIds);
|
||||
machineIds.forEach(id -> {
|
||||
verify(machineInfoService, times(1)).update(any());
|
||||
});
|
||||
}
|
||||
|
||||
// ======================= 更新密钥测试 =======================
|
||||
@Test
|
||||
void testUpdateSecretKey_WithNewFile_ShouldUpdatePath() throws IOException {
|
||||
MockMultipartFile newFile = new MockMultipartFile(
|
||||
"file",
|
||||
"new_key.pem",
|
||||
"application/octet-stream",
|
||||
new ByteArrayInputStream("new content".getBytes())
|
||||
);
|
||||
when(aliOssUtil.save(newFile)).thenReturn("new_key.pem");
|
||||
|
||||
validSecretKeyDto.setId(TEST_SECRET_KEY_ID);
|
||||
boolean result = secretKeyService.updateSecretKey(validSecretKeyDto, newFile);
|
||||
|
||||
// 验证 OSS 调用和路径更新
|
||||
verify(aliOssUtil, times(1)).save(newFile);
|
||||
SecretKey updatedKey = new SecretKey();
|
||||
BeanUtils.copyProperties(validSecretKeyDto, updatedKey);
|
||||
updatedKey.setFileName("new_key.pem");
|
||||
updatedKey.setPath("https://bucket.endpoint/new_key.pem");
|
||||
verify(secretServiceMapper, times(1)).updateById(updatedKey);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ======================= 删除密钥测试 =======================
|
||||
@Test
|
||||
void testDeleteSecretKey_ValidId_ShouldDeleteFileAndRecord() {
|
||||
SecretKey secretKey = new SecretKey();
|
||||
secretKey.setId(TEST_SECRET_KEY_ID);
|
||||
secretKey.setFileName(TEST_FILE_NAME);
|
||||
when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
|
||||
|
||||
boolean result = secretKeyService.deleteSecretKey(TEST_SECRET_KEY_ID);
|
||||
|
||||
// 验证 OSS 删除和 Mapper 调用
|
||||
verify(aliOssUtil, times(1)).deleteFile(TEST_FILE_NAME);
|
||||
verify(secretServiceMapper, times(1)).deleteById(TEST_SECRET_KEY_ID);
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ======================= 列表查询测试 =======================
|
||||
@Test
|
||||
void testListSecretKey_WithNameFilter_ShouldReturnMatchedRecords() {
|
||||
SecretKeyDto queryDto = new SecretKeyDto();
|
||||
queryDto.setName("Test");
|
||||
QueryWrapper<SecretKey> wrapper = new QueryWrapper<>();
|
||||
wrapper.like("name", "Test");
|
||||
|
||||
when(secretServiceMapper.selectPage(any(), any())).thenReturn(new Page<>());
|
||||
|
||||
secretKeyService.listSecretKey(queryDto);
|
||||
verify(secretServiceMapper, times(1)).selectPage(any(), wrapper);
|
||||
}
|
||||
|
||||
// ======================= 文件下载测试 =======================
|
||||
@Test
|
||||
void testDownloadSecretKeyFile_ValidId_ShouldReturnResponseEntity() throws IOException {
|
||||
SecretKey secretKey = new SecretKey();
|
||||
secretKey.setFileName(TEST_FILE_NAME);
|
||||
when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
|
||||
InputStreamResource inputStreamResource = new InputStreamResource(
|
||||
new ByteArrayInputStream("test content".getBytes())
|
||||
);
|
||||
when(aliOssUtil.downloadFile(TEST_FILE_NAME)).thenReturn(
|
||||
ResponseEntity.ok(inputStreamResource)
|
||||
);
|
||||
|
||||
ResponseEntity<InputStreamResource> response = secretKeyService.downloadSecretKeyFile(TEST_SECRET_KEY_ID);
|
||||
assertNotNull(response);
|
||||
assertEquals("test content", Utils.toString(response.getBody().getInputStream()));
|
||||
}
|
||||
|
||||
// ======================= 批量删除测试 =======================
|
||||
@Test
|
||||
void testDeleteList_ValidIds_ShouldSubmitAsyncDelete() {
|
||||
List<Long> ids = Collections.singletonList(TEST_SECRET_KEY_ID);
|
||||
SecretKey secretKey = new SecretKey();
|
||||
secretKey.setFileName(TEST_FILE_NAME);
|
||||
when(secretServiceMapper.selectBatchIds(ids)).thenReturn(Collections.singletonList(secretKey));
|
||||
|
||||
secretKeyService.deleteList(ids);
|
||||
|
||||
// 验证异步任务提交
|
||||
verify(secretKeyService.FILE_DELETE_EXECUTOR, times(1)).execute(any(Runnable.class));
|
||||
verify(secretServiceMapper, times(1)).deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
// ======================= 异常处理测试 =======================
|
||||
@Test
|
||||
void testDeleteSecretKey_FileDeleteFailed_ShouldThrowException() {
|
||||
aliOssUtil.deleteFile(TEST_FILE_NAME);
|
||||
SecretKey secretKey = new SecretKey();
|
||||
secretKey.setId(TEST_SECRET_KEY_ID);
|
||||
secretKey.setFileName(TEST_FILE_NAME);
|
||||
when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
secretKeyService.deleteSecretKey(TEST_SECRET_KEY_ID);
|
||||
}, "删除文件失败");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user