机器管理
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" />
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
<outputRelativeToContentRoot value="true" />
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="machine-management-module" />
|
||||||
</profile>
|
</profile>
|
||||||
<profile name="Annotation profile for ops-pro" enabled="true">
|
<profile name="Annotation profile for ops-pro" enabled="true">
|
||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
@ -53,6 +54,7 @@
|
|||||||
<module name="module-ci-dispatch-api" target="17" />
|
<module name="module-ci-dispatch-api" target="17" />
|
||||||
<module name="module-ci-environment" target="17" />
|
<module name="module-ci-environment" target="17" />
|
||||||
<module name="module-ci-event" 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-log" target="17" />
|
||||||
<module name="module-ci-market" target="17" />
|
<module name="module-ci-market" target="17" />
|
||||||
<module name="module-ci-project" target="17" />
|
<module name="module-ci-project" target="17" />
|
||||||
@ -69,6 +71,7 @@
|
|||||||
<module name="app-plugins" options="-parameters" />
|
<module name="app-plugins" options="-parameters" />
|
||||||
<module name="commons" options="-parameters" />
|
<module name="commons" options="-parameters" />
|
||||||
<module name="framework" 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-commons" options="-parameters" />
|
||||||
<module name="module-ci-engine" options="-parameters" />
|
<module name="module-ci-engine" options="-parameters" />
|
||||||
<module name="module-ci-plugin" 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/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/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/framework/src/main/resources" 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/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-pipeline/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common/src/main/java" 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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="RemoteRepositoriesConfiguration">
|
<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>
|
<remote-repository>
|
||||||
<option name="id" value="central" />
|
<option name="id" value="central" />
|
||||||
<option name="name" value="Central Repository" />
|
<option name="name" value="Central Repository" />
|
||||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
</remote-repository>
|
</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>
|
<remote-repository>
|
||||||
<option name="id" value="aliyunmaven" />
|
<option name="id" value="aliyunmaven" />
|
||||||
<option name="name" value="aliyun" />
|
<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-store-api/pom.xml" />
|
||||||
<option value="$PROJECT_DIR$/modules/module-ci-process-biz/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/module-ci-process-api/pom.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/modules/ee/machine-management-module/pom.xml" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
<option name="ignoredFiles">
|
<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