OCT REM: 1. 初始化Agent框架Java SpringBoot3

This commit is contained in:
HuangXin 2025-01-08 15:41:15 +08:00
parent 8b4aa29f8f
commit 10d91f9c8d
91 changed files with 8475 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

37
.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
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/
**/logs/
**/git.properties
**/logs/*.log
/.mvn/

View File

@ -0,0 +1,43 @@
server :
port : 9276
servlet :
context-path: /cmhi
compression:
# 开启响应压缩
enabled : true
mime-types :
- application/json # RESTful API JSON
# 进行压缩的最小体积
min-response-size: 1KB
# Crypto Configure
jasypt :
encryptor:
algorithm: PBEWITHHMACSHA512ANDAES_256
password :
spring :
mvc :
throw-exception-if-no-handler-found: true
web :
resources:
add-mappings: false
jackson:
date-format : yyyy-MM-dd HH:mm:ss.SSS
timezone : GMT+8
default-property-inclusion: non_null
mapper :
default-view-inclusion: true
deserialization :
fail-on-unknown-properties: false
log4j :
logger:
org:
mybatis: info
# swagger-ui custom path
springdoc:
swagger-ui:
path: /swagger-ui.html

View File

@ -0,0 +1,3 @@
#config log
logging:
config: file:config/logback.xml

View File

@ -0,0 +1,6 @@
#config log
logging:
config: file:./config/logback.xml
common :
locale: en_US

View File

@ -0,0 +1,11 @@
common :
token-expired-of-seconds: 600
allow-passwd-retry-times: 3
show-sql-command : false
locale : zh_CN
protocol:
check-timestamp : true
timeout-of-seconds: 600
crypto-type : 0
#crypto-key: 12354

3
config/application.yml Normal file
View File

@ -0,0 +1,3 @@
spring:
profiles:
active: local, common, user

108
config/logback.xml Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true">
<property name="LOG_PATH" value="./logs"/>
<property name="LOG_LEVEL" value="info"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %m%n</pattern>
</encoder>
</appender>
<appender name="BIZ"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/biz.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/biz.log.%d{yyyyMMdd}
</fileNamePattern>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %m%n</pattern>
</encoder>
</appender>
<appender name="SYSTEM-LOG-FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/system.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/system.log.%d{yyyyMMdd}
</fileNamePattern>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %m%n</pattern>
</encoder>
</appender>
<appender name="DATA"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/data.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/data.log.%d{yyyyMMdd}
</fileNamePattern>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %m%n</pattern>
</encoder>
</appender>
<logger name="com.cmhi.gds.mapper" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.mybatis" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.apache.ibatis" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="org.mybatis.spring" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="org.springframework.jdbc" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.springframework.orm" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="com.mysql" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="java.sql" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="javax.sql" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="org.springframework.security" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.casbin.jcasbin" level="error" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.ulisesbocchio.jasyptspringboot" level="error" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<root level="${LOG_LEVEL}">
<appender-ref ref="SYSTEM-LOG-FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

259
mvnw vendored Normal file
View 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
mvnw.cmd vendored Normal file
View 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"

220
pom.xml Normal file
View File

@ -0,0 +1,220 @@
<?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.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cmhi</groupId>
<artifactId>middleware-agent</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>middleware-agent</name>
<description>middleware-agent</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>26.0.0</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<version>2.2.27</version>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core-java11</artifactId>
<version>6.6.5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.21</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>4.9.10</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<goals>
<goal>revision</goal>
</goals>
<phase>initialize</phase>
</execution>
</executions>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
<verbose>false</verbose>
<offline>true</offline>
<dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.basedir}/src/main/resources/git.properties
</generateGitPropertiesFilename>
<excludeProperties>
<excludeProperty>git.commit.message.*</excludeProperty>
<excludeProperty>git.commit.user.*</excludeProperty>
<excludeProperty>git.remote.origin.*</excludeProperty>
</excludeProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dspring.config.location=file:${project.basedir}/config/</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,44 @@
package com.cmhi.magent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主应用程序启动类 {@code MiddlewareAgentApplication}
* <p>
* 本类是基于 Spring Boot 的应用程序入口标注了 {@link SpringBootApplication} 注解
* 自动启用了 Spring Boot 的组件扫描自动配置以及其他核心功能
* 通过运行此类的 {@code main} 方法启动整个应用程序
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>启动 Spring Boot 应用程序</li>
* <li>加载应用程序上下文并初始化相关组件</li>
* </ul>
* </p>
*
* @see SpringBootApplication
* @see SpringApplication
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@SpringBootApplication
public class MiddlewareAgentApplication {
/**
* 应用程序启动入口
* <p>
* 该方法通过调用 {@link SpringApplication#run(Class, String...)} 方法启动整个 Spring Boot 应用程序
* 运行后Spring 应用上下文将被加载并启动所有相关的 Spring 组件 Web 控制器服务配置等
* </p>
*
* @param args 命令行参数可以通过 JVM 启动选项传递给应用程序
*/
public static void main(String[] args) {
SpringApplication.run(MiddlewareAgentApplication.class, args);
}
}

View File

@ -0,0 +1,30 @@
package com.cmhi.magent.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 解密协议注解
*
* <p>该注解用于标记需要解密处理的类或方法使用该注解后可通过拦截器或 AOP 等机制
* 实现对请求或响应数据的解密逻辑</p>
*
* <p>使用场景
* <ul>
* <li>标记在控制器方法上时表示该方法的请求参数或响应数据需要解密</li>
* <li>标记在类上时表示该类中所有的方法均需要进行解密处理</li>
* </ul>
* </p>
*
* <p>该注解的保留策略为 {@code RUNTIME}允许在运行时通过反射机制获取注解信息</p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptionProtocol {
}

View File

@ -0,0 +1,33 @@
package com.cmhi.magent.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 加密协议注解
*
* <p>该注解用于标记需要加密处理的类或方法使用该注解后可通过拦截器或 AOP 等机制
* 实现对请求或响应数据的加密逻辑</p>
*
* <p>使用场景
* <ul>
* <li>标记在控制器方法上时表示该方法的请求参数或响应数据需要进行加密</li>
* <li>标记在类上时表示该类中所有的方法均需要进行加密处理</li>
* </ul>
* </p>
*
* <p>该注解的保留策略为 {@code RUNTIME}允许在运行时通过反射机制获取注解信息</p>
*
* <p>配合 {@link DecryptionProtocol} 注解使用时可实现加密和解密的全流程数据处理</p>
*
* @see DecryptionProtocol
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptionProtocol {
}

View File

@ -0,0 +1,68 @@
package com.cmhi.magent.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 操作日志注解
*
* <p>该注解用于标记需要记录操作日志的方法或参数在标注的方法执行时可以通过 AOP 或其他拦截机制
* 自动记录操作日志信息包括操作模块操作类型和操作说明</p>
*
* <p>使用场景
* <ul>
* <li>标记在方法上时用于记录该方法涉及的操作模块类型及描述</li>
* <li>标记在方法参数上时可配合日志框架提取参数中的操作信息</li>
* </ul>
* </p>
*
* <p>通过运行时保留策略{@code RUNTIME}注解信息可在程序运行时通过反射机制获取</p>
*
* <p>核心字段说明
* <ul>
* <li>{@code OperationModule}操作模块的名称用于标识业务模块或功能点</li>
* <li>{@code OperationType}操作的类型新增修改删除等</li>
* <li>{@code OperationDesc}操作的详细描述便于日志分析</li>
* </ul>
* </p>
*
* @author
* @version 1.0.0
* @since 2025-01-07
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SuppressWarnings("java:S100")
public @interface OperationLogAnnotation {
/**
* 操作模块
*
* <p>用于标识业务模块或功能点用户管理订单管理</p>
*
* @return 操作模块的名称默认为空字符串
*/
String OperationModule() default "";
/**
* 操作类型
*
* <p>用于记录操作的具体类型新增新建修改删除等</p>
*
* @return 操作类型默认为空字符串
*/
String OperationType() default "";
/**
* 操作说明
*
* <p>对操作的详细描述便于后续日志排查和分析</p>
*
* @return 操作说明默认为空字符串
*/
String OperationDesc() default "";
}

View File

@ -0,0 +1,29 @@
package com.cmhi.magent.common;
/**
* 定义通用的枚举接口
* <p>
* {@code BaseEnum} 接口提供了获取枚举值和描述的抽象方法所有实现该接口的枚举类需要提供具体实现
* <p>
* 此接口适用于统一管理枚举类型的数据便于在项目中实现标准化的处理逻辑
* <p>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public interface BaseEnum {
/**
* 获取枚举的值
* <p>
* @return 枚举值通常是一个唯一的整数
*/
Integer getValue();
/**
* 获取枚举的描述信息
* <p>
* @return 描述信息的字符串
*/
String getDescription();
}

View File

@ -0,0 +1,77 @@
package com.cmhi.magent.common;
import java.util.Arrays;
import java.util.List;
/**
* 枚举处理器工具类用于操作实现 {@code BaseEnum} 接口的枚举类型
* <p>
* {@code CommonEnumHandler} 提供了统一的枚举管理功能包括提取枚举值和通过值查找枚举对象的功能
* 这种设计使得业务代码在处理通用枚举逻辑时更加简洁和高效
*
* <p> 泛型约束<br>
* - {@code E} 必须是实现了 {@code BaseEnum} 接口的枚举类型
* <p>
* @param <E> 枚举类型必须同时是 {@code Enum} {@code BaseEnum}
* <p>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public final class CommonEnumHandler<E extends BaseEnum> {
/**
* 被处理的枚举类型
*/
private final Class<E> enumType;
/**
* 枚举值的列表
*/
private final List<E> enums;
/**
* 构造函数根据枚举类型初始化枚举处理器
*
* <p>此构造函数会提取所有的枚举常量并存储为列表以供后续操作
*
* @param type 枚举类型不能为 {@code null}
* @throws IllegalArgumentException 如果 {@code type} 参数为 {@code null}
*/
public CommonEnumHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.enumType = type;
this.enums = Arrays.asList(type.getEnumConstants());
}
/**
* 根据枚举值查找对应的枚举常量
*
* <p>此静态方法可以从枚举类中根据值code高效查找对应的枚举对象
*
* @param <E> 枚举类型必须同时实现 {@code Enum} {@code BaseEnum}
* @param enumClass 枚举类不能为 {@code null}
* @param code 枚举值code用于匹配枚举常量
* @return 匹配的枚举对象如果未找到则返回 {@code null}
* @throws IllegalArgumentException 如果 {@code enumClass} 参数为 {@code null}
*/
public static <E extends Enum<?> & BaseEnum> E codeOf(Class<E> enumClass, int code) {
if (enumClass == null) {
throw new IllegalArgumentException("Enum class cannot be null");
}
E[] enumConstants = enumClass.getEnumConstants();
for (E e : enumConstants) {
// 使用 Integer 比较以更安全地处理可能的 null
if (e.getValue() != null && e.getValue().equals(code)) {
return e;
}
}
return null;
}
}

View File

@ -0,0 +1,103 @@
package com.cmhi.magent.common;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.MessageUtil;
/**
* 通用状态枚举类
*
* <p>该枚举表示实体或操作的通用状态包括正常已锁定已禁用已删除等状态
* 每个状态都包含一个唯一的数值`code`和对应的状态描述`readme`
*
* <p>本枚举实现了 {@code BaseEnum} 接口提供了一致的接口方法用于获取状态值和描述信息
* 另外通过 {@code MessageUtil} {@code ApiContextUtils} 实现了国际化支持
* <p>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public enum CommonStatus implements BaseEnum {
/**
* 正常状态
*
* <p>状态码{@code 0}
*/
NORMAL(0, "正常"),
/**
* 已锁定状态
*
* <p>状态码{@code 1}
*/
LOCKED(1, "已锁定"),
/**
* 已禁用状态
*
* <p>状态码{@code 2}
*/
DISABLED(2, "已禁用"),
/**
* 已删除状态
*
* <p>状态码{@code 3}
*/
DELETED(3, "已删除");
/**
* 状态对应的唯一数值
*/
private final Integer code;
/**
* 状态的默认描述信息
*/
private final String readme;
/**
* 构造函数用于初始化状态的数值和描述信息
*
* @param code 状态码
* @param readme 状态描述信息
*/
CommonStatus(int code, String readme) {
this.code = code;
this.readme = readme;
}
/**
* 获取状态的枚举名称
*
* <p>例如当状态为 {@code NORMAL} 返回 "NORMAL"
*
* @return 状态的枚举名称字符串
*/
public String getStringValue() {
return this.name();
}
/**
* 获取状态的数值
*
* @return 状态对应的数值`code`
*/
@Override
public Integer getValue() {
return this.code;
}
/**
* 获取状态的描述信息支持国际化
*
* <p>描述信息通过 {@code MessageUtil} {@code ApiContextUtils} 动态获取
* 以支持多语言环境
*
* @return 状态的描述信息
*/
@Override
public String getDescription() {
return MessageUtil.get(getStringValue(), ApiContextUtils.getLanguage());
}
}

View File

@ -0,0 +1,102 @@
package com.cmhi.magent.common;
/**
* 常量值工具类主要用于存储项目中使用的全局静态常量
*
* <p>该类包含正则表达式时间相关的常量HTTP头字段常量以及其他通用数据
* 所有的常量均为 {@code static final}不可修改
* <p>
* 本类为工具类禁止实例化
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public final class ConstValue {
/**
* 私有构造函数防止实例化
*
* <p>尝试实例化该类时将抛出 {@code AssertionError}
*/
private ConstValue() {
throw new AssertionError("This is a utility class and cannot be instantiated.");
}
/**
* 用于验证输入字符串的正则表达式禁止包含非法字符
*
* <p>匹配规则字符串中不包含以下字符空格`--``*``%``+`单引号`'`和分号`;`
*/
public static final String UN_EXPECT_REGEX_CHARS = "^((?!(--|\\s|\\*|%|\\+|'|;])).)*$";
/**
* HTTP认证头部的前缀字符串
*
* <p>通常用于在请求头中添加认证令牌例如 {@code Authorization: Bearer <token>}
*/
public static final String STRING_HTTP_AUTH_HEAD = "Bearer ";
/**
* HTTP请求头中的语言标识字段
*
* <p>该字段通常用于传递客户端的语言环境例如 {@code zh-CN} {@code en-US}
*/
public static final String LANGUAGE_HEAD = "language";
/**
* 一秒的毫秒数1000毫秒
*/
public static final long MS_OF_SECOND = 1000L;
/**
* 一分钟的秒数60秒
*/
public static final long SECOND_OF_MINUTE = 60L;
/**
* IPv4地址的最大长度
*
* <p>IPv4地址的长度范围为 7 15 字符例如 `255.255.255.255`
*/
public static final int MAX_IPV4_LEN = 15;
/**
* 匹配IP地址的正则表达式支持IPv4和IPv6格式
*
* <p>匹配规则
* <ul>
* <li>IPv4地址 {@code 192.168.1.1}</li>
* <li>IPv6地址 {@code 2001:0db8:85a3:0000:0000:8a2e:0370:7334}</li>
* <li>简化形式的IPv6地址 {@code ::1} {@code 2001:db8::1}</li>
* </ul>
*/
public static final String IP_ADDRESS_REGEX =
"^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$" +
"|^([\\da-fA-F]{1,4}:){7}[\\da-fA-F]{1,4}$" +
"|^::([\\da-fA-F]{1,4}:){0,6}[\\da-fA-F]{1,4}$" +
"|^([\\da-fA-F]{1,4}:){1,7}:$" +
"|^([\\da-fA-F]{1,4}:){1,6}:[\\da-fA-F]{1,4}$";
/**
* 协议相关的常量
*
* <p>该内部类用于存储与通信协议相关的常量
*/
public static final class Protocol {
/**
* 协议的版本号
*/
public static final int VERSION = 1;
/**
* 私有构造函数防止实例化
*
* <p>尝试实例化该类时将抛出 {@code AssertionError}
*/
private Protocol() {
throw new AssertionError("This is a utility class and cannot be instantiated.");
}
}
}

View File

@ -0,0 +1,255 @@
package com.cmhi.magent.common;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.MessageUtil;
import jakarta.servlet.http.HttpServletResponse;
/**
* {@code ErrorCode} 定义应用程序中的错误代码和描述
* <p>
* 此枚举类提供了各种错误代码及其对应的描述信息同时支持获取相关的 HTTP 状态码
* <p>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public enum ErrorCode implements BaseEnum {
/** 成功 */
ERR_OK(0, "成功"),
/** 密码错误 */
ERR_PASSWORD(1, "密码错误"),
/** 用户不存在 */
ERR_USERNOTFOUND(2, "用户不存在"),
/** 连续密码错误达上限,再次输入错误将锁定用户 */
ERR_PASSWORDMORE(3, "连续密码错误达上限,再次输入错误将锁定用户"),
/** 密码错误达上限,用户被锁定 */
ERR_USERLOCK(4, "密码错误达上限,用户被锁定"),
/** 密码已经过期 */
ERR_PASSWORD_EXPIRED(5, "密码已经过期"),
/** 用户账户异常 */
ERR_ACCOUNT(6, "用户账户异常"),
/** 该用户已经存在 */
ERR_USEREXIST(7, "该用户已经存在"),
/** 用户密码强度不符合要求 */
ERR_PASSWORDSIMPLE(8, "用户密码强度不符合要求"),
/** 输入信息格式有误 */
ERR_INPUTFORMAT(9, "输入信息格式有误"),
/** 缺少必要输入信息 */
ERR_INPUTMISS(10, "缺少必要输入信息"),
/** 操作员权限不足 */
ERR_PERMISSION(11, "操作员权限不足"),
/** 请求超时 */
ERR_REQTIMEOUT(12, "请求超时"),
/** 参数错误 */
ERR_PARAMS(13, "参数错误"),
/** 系统异常 */
ERR_SYSTEMEXCEPTION(14, "系统异常"),
/** 未知命令 */
ERR_UNKNOWNCMD(15, "未知命令"),
/** 用户未登录 */
ERR_LOGOUT(16, "用户未登录"),
/** Token 超时 */
ERR_TOKENTIMEOUT(17, "Token超时"),
/** 非法 Token */
ERR_TOKENNOTFOUND(18, "非法Token"),
/** Token 秘钥错误 */
ERR_TOKEN_KEY(19, "Token 秘钥错误"),
/** Http 请求缺少认证头部 */
ERR_MISSAUTHHEAD(20, "Http 请求缺少认证头部"),
/** 没有该内容 */
ERR_NOSUCHITEM(21, "没有该内容"),
/** 该内容已经存在 */
ERR_ITEMEXISTS(22, "该内容已经存在"),
/** 参数异常 */
ERR_PARAMEXCEPTION(23, "参数异常"),
/** 设备已锁定 */
ERR_DEVICELOCKED(24, "设备已锁定"),
/** 协议版本不兼容,请升级系统 */
ERR_VERSION(25, "协议版本不兼容,请升级系统"),
/** 没有这个类型的设备 */
ERR_NOSUCHTYPE(26, "没有这个类型的设备"),
/** 禁止同时删除多个设备 */
ERR_REMOVEMORE(27, "禁止同时删除多个设备"),
/** 同类任务正在运行 */
ERR_TASKRUNNING(28, "同类任务正在运行"),
/** 不支持的操作 */
ERR_UNSUPPORT(29, "不支持的操作"),
/** 操作中断 */
ERR_INTERRUPT(30, "操作中断"),
/** 调用设备失败 */
ERR_CALLDEVICE(31, "调用设备失败"),
/** 没有该任务 */
ERR_NOSUCHTASK(32, "没有该任务"),
/** 该任务没有运行 */
ERR_TASKNOTRUNNING(33, "该任务没有运行"),
/** 请求超时 */
ERR_REQUESTTIMEOUT(34, "请求超时"),
/** 无法处置该 IP */
ERR_UNABLEDISPOSEIP(35, "无法处置该IP"),
/** 操作数据库失败 */
ERR_DATABASE(36, "操作数据库失败"),
/** 未经授权的客户端 */
ERR_UNTRUSTHOST(37, "未经授权的客户端"),
/** 未经授权的 Token */
ERR_UNTRUSTTOKEN(38, "未经授权的Token"),
/** 未提供该接口 */
ERR_UNKNOWNINTERFACE(39, "未提供该接口"),
/** BASE64 解密失败 */
ERR_DECRYPT_BASE64(40, "BASE64解密失败"),
/** BASE64 加密失败 */
ERR_ENCRYPT_BASE64(41, "BASE64加密失败"),
/** AES128 解密失败 */
ERR_DECRYPT_AES128(42, "AES128解密失败"),
/** AES128 加密失败 */
ERR_ENCRYPT_AES128(43, "AES128加密失败"),
/** 3DES 解密失败 */
ERR_DECRYPT_3DES(44, "3DES解密失败"),
/** 3DES 加密失败 */
ERR_ENCRYPT_3DES(45, "3DES加密失败"),
/** 不支持的解密算法 */
ERR_DECRYPT_UNKNOWN(46, "不支持的解密算法"),
/** 不支持的加密算法 */
ERR_ENCRYPT_UNKNOWN(47, "不支持的加密算法"),
/** Json 序列化错误 */
ERR_JSON_ENCODE(48, "Json 序列号错误"),
/** Json 反序列化错误 */
ERR_JSON_DECODE(49, "Json 反序列化错误"),
/** AES256 加密失败 */
ERR_ENCRYPT_AES256(50, "AES256加密失败"),
/** AES256 解密失败 */
ERR_DECRYPT_AES256(51, "AES256解密失败"),
/** 错误的秘钥 */
ERR_CRYPTO_KEY(52, "错误的秘钥"),
/** 用户角色不存在 */
ERR_USER_ROLE_NOTEXISTS(53, "用户角色不存在"),
/** 资源被占用 */
ERR_RESOURCE_USED(54, "资源被占用"),
;
private final int errCode;
private final String errMsg;
/**
* 构造函数
* <p>
* @param err 错误代码
* @param msg 错误消息
*/
ErrorCode(int err, String msg) {
this.errCode = err;
this.errMsg = msg;
}
/**
* 获取错误代码
* <p>
* @return 错误代码
*/
public int getCode() {
return errCode;
}
/**
* 获取对应的 HTTP 状态码
* <p>
* 根据错误代码映射到适当的 HTTP 状态码
* <p>
* @return HTTP 状态码
*/
public int getHttpCode() {
return switch (this) {
case ERR_OK -> HttpServletResponse.SC_OK;
case ERR_SYSTEMEXCEPTION, ERR_PARAMEXCEPTION -> HttpServletResponse.SC_EXPECTATION_FAILED;
case ERR_TOKENTIMEOUT, ERR_REQTIMEOUT -> HttpServletResponse.SC_REQUEST_TIMEOUT;
case ERR_UNTRUSTTOKEN, ERR_UNTRUSTHOST, ERR_LOGOUT -> HttpServletResponse.SC_UNAUTHORIZED;
case ERR_MISSAUTHHEAD, ERR_PARAMS, ERR_INPUTFORMAT, ERR_INPUTMISS -> HttpServletResponse.SC_BAD_REQUEST;
case ERR_UNSUPPORT -> HttpServletResponse.SC_METHOD_NOT_ALLOWED;
case ERR_UNKNOWNINTERFACE -> HttpServletResponse.SC_NOT_FOUND;
default -> HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
};
}
/**
* 获取错误名称
* <p>
* @return 错误名称
*/
public String getStringValue() {
return this.name();
}
/**
* 获取错误代码值
* <p>
* @return 错误代码值
*/
@Override
public Integer getValue() {
return this.errCode;
}
/**
* 获取错误描述
* <p>
* @return 错误描述
*/
@Override
public String getDescription() {
return MessageUtil.get(getStringValue(), ApiContextUtils.getLanguage());
}
}

View File

@ -0,0 +1,110 @@
package com.cmhi.magent.common;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.MessageUtil;
/**
* 协议加密类型枚举类
*
* <p>该枚举定义了不同的协议加密方式包括不加密Base64编码AES加密128位和256位以及DES加密
* 每种加密类型均包含对应的编码值`code`和默认描述信息`readme`
*
* <p>本枚举实现了 {@code BaseEnum} 接口提供了统一的接口方法用以获取编码值和描述信息
* 同时通过 {@code MessageUtil} {@code ApiContextUtils} 实现了国际化支持
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public enum ProtoCryptoType implements BaseEnum {
/**
* 不加密
*
* <p>编码值{@code 0}
*/
CRYPTO_NONE(0, "不加密"),
/**
* Base64编码
*
* <p>编码值{@code 1}
*/
CRYPTO_BASE64(1, "Base64编码"),
/**
* AES128加密
*
* <p>编码值{@code 2}
*/
CRYPTO_AES128(2, "AES128加密"),
/**
* DES对称加密
*
* <p>编码值{@code 3}
*/
CRYPTO_DES(3, "DES对称加密"),
/**
* AES256加密
*
* <p>编码值{@code 4}
*/
CRYPTO_AES256(4, "AES256加密");
/**
* 加密方式的唯一编码值
*/
private final int code;
/**
* 加密方式的默认描述信息
*/
private final String readme;
/**
* 构造函数用于初始化加密类型的编码值和描述信息
*
* @param code 加密方式的编码值
* @param readme 加密方式的描述信息
*/
ProtoCryptoType(int code, String readme) {
this.code = code;
this.readme = readme;
}
/**
* 获取加密方式的描述信息
*
* <p>此方法直接返回枚举定义时的描述信息`readme`
*
* @return 加密方式的描述信息
*/
public String getStringValue() {
return this.readme;
}
/**
* 获取加密方式的编码值
*
* @return 加密方式的编码值`code`
*/
@Override
public Integer getValue() {
return this.code;
}
/**
* 获取加密方式的描述信息支持国际化
*
* <p>描述信息通过 {@code MessageUtil} {@code ApiContextUtils} 动态获取
* 从而支持多语言环境下的描述值
*
* @return 加密方式的国际化描述信息
*/
@Override
public String getDescription() {
return MessageUtil.get(getStringValue(), ApiContextUtils.getLanguage());
}
}

View File

@ -0,0 +1,106 @@
package com.cmhi.magent.config;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.net.InetAddress;
import java.util.Locale;
/**
* @class CommonConfigure
* @brief 配置类用于管理通用的应用程序配置
* <p>
* 此类用于加载和管理应用中常见的配置项例如令牌过期时间本地化设置等
* 并提供全局变量初始化功能
* <p>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@ConfigurationProperties(prefix = "common")
@Configuration
@Slf4j
@SuppressWarnings({"java:S3008", "java:S1444", "java:S1104"})
public class CommonConfigure {
/** 全局变量,基准 URL */
public static String BASEURL;
/** 全局变量,项目的前缀 URL */
public static String PROJECT_PREFIX_URL;
/** 注入的 Spring Boot 服务器属性 */
@Resource
private ServerProperties serverProperties;
/** 令牌过期时间(单位:秒) */
private Integer tokenExpiredOfSeconds;
/** 密码允许错误次数 */
private Integer allowPasswdRetryTimes;
/** 本地化语言设置 */
private String locale;
/** 是否显示 SQL 命令日志 */
private boolean showSqlCommand;
/**
* 设置全局变量
* <p>
* @param prefixUrl 项目前缀 URL
* @param baseUrl 基础 URL
*/
private static void setGlobalVars(String prefixUrl, String baseUrl) {
PROJECT_PREFIX_URL = prefixUrl;
BASEURL = baseUrl;
}
/**
* 获取默认的 Locale 设置
* <p>
* 根据配置的 `locale` 字符串解析为对应的 Locale如果格式不正确则返回系统默认的 {@link Locale#CHINA}
* <p>
* @return 配置的 Locale 对象
*/
public Locale getDefaultLocale() {
// 按下划线分割
String[] parts = locale.split("_");
if (2 == parts.length) {
return new Locale(parts[0], parts[1]);
}
// 如果格式不正确则返回系统默认 Locale.CHINA
return Locale.CHINA;
}
/**
* 初始化全局变量
* <p>
* Spring 容器加载时通过 @PostConstruct 注解调用该方法
* 完成全局变量的初始化和本地化设置
*/
@PostConstruct
private void initGlobalValue() {
log.info("Current: tokenExpiredOfSeconds = {}, allowPasswdRetryTimes = {}, showSqlCommand = {}",
tokenExpiredOfSeconds, allowPasswdRetryTimes, showSqlCommand);
String addr = "localhost";
try {
addr = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
log.error("Unable get local ip address: {}", e.getMessage());
} finally {
setGlobalVars(serverProperties.getServlet().getContextPath(),
"http://" + addr + ":" + serverProperties.getPort() + serverProperties.getServlet().getContextPath());
log.info("baseUrl: {}", BASEURL);
}
Locale.setDefault(getDefaultLocale());
}
}

View File

@ -0,0 +1,76 @@
package com.cmhi.magent.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* 国际化与本地化配置类
*
* <p>该类负责配置 Spring 的国际化功能包括消息资源文件的加载和校验消息的国际化支持
* 配置的资源文件路径和名称可以根据项目需求进行调整
* <p>
* 项目中的国际化实现依赖于 Spring {@code MessageSource} JSR-303 校验框架
* 通过此配置类开发者可以动态加载不同语言的文本信息并支持多语言环境的表单校验消息
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Configuration
@Slf4j
public class LocaleConfig {
/**
* 配置主消息资源文件的加载
*
* <p>该方法定义了一个 {@code ResourceBundleMessageSource} Bean 用于加载主要的国际化资源文件
* `messages``errorMessage``enumMessage` `syslogMessage` 资源文件位于 `i18n` 目录下
* </p>
*
* @return 配置好的 {@code ResourceBundleMessageSource} 实例
*/
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
// 设置国际化文件存储路径和资源文件的前缀名
source.setBasenames("i18n/message", "i18n/errorMessage", "i18n/enumMessage", "i18n/syslogMessage");
// 当找不到对应的 key 值时返回 key 本身作为默认信息
source.setUseCodeAsDefaultMessage(true);
// 设置默认的字符编码为 UTF-8支持多语言字符
source.setDefaultEncoding("UTF-8");
return source;
}
/**
* 配置校验框架的消息国际化支持
*
* <p>该方法定义了一个 {@code LocalValidatorFactoryBean} Bean用于为 JSR-303 校验框架
* 提供国际化支持例如可以将校验注解的错误消息动态地从国际化资源文件中获取
* </p>
*
* @return 配置好的 {@code LocalValidatorFactoryBean} 实例
*/
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
// 初始化校验工厂 Bean
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
// 使用 ReloadableResourceBundleMessageSource 动态加载国际化消息文件
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
// 设置校验消息的资源文件路径i18n/validationMessage
messageSource.setBasename("i18n/validationMessage");
// 设置消息文件的默认字符编码为 UTF-8
messageSource.setDefaultEncoding("UTF-8");
// 将校验消息源设置到校验工厂 Bean
localValidatorFactoryBean.setValidationMessageSource(messageSource);
return localValidatorFactoryBean;
}
}

View File

@ -0,0 +1,58 @@
package com.cmhi.magent.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 提供全局的 ObjectMapper 实例访问
*
* <p>该类实现了 {@code ApplicationContextAware} 接口用于从 Spring 容器中获取 ObjectMapper Bean
* 通过该类可以方便地在需要的地方获取 ObjectMapper 实例而不需要多次注入或手动创建
* </p>
*
* <p>ObjectMapper Jackson 的核心类用于处理 JSON 数据的序列化和反序列化
* 通过与 Spring 容器的集成确保 ObjectMapper 使用了统一的配置
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Component
public class ObjectMapperProvider implements ApplicationContextAware {
/**
* 应用上下文对象用于获取 Spring 容器中的 Bean
*/
@Setter
private static ApplicationContext context;
/**
* 设置应用上下文
*
* <p>该方法由 Spring 容器在启动时调用用于将 ApplicationContext 注入到当前类中
* </p>
*
* @param applicationContext Spring 应用上下文对象
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
setContext(applicationContext);
}
/**
* 获取全局的 ObjectMapper 实例
*
* <p>该方法从 Spring 容器中获取 ObjectMapper Bean确保所有地方使用的 ObjectMapper
* 均为同一个实例避免重复创建和配置不一致
* </p>
*
* @return 全局唯一的 ObjectMapper 实例
*/
public static ObjectMapper getMapper() {
return context.getBean(ObjectMapper.class);
}
}

View File

@ -0,0 +1,82 @@
package com.cmhi.magent.config;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* 项目 Git 版本信息配置类
*
* <p>该类读取并存储项目在构建过程中生成的 Git 信息包括提交 ID提交时间分支名称
* 构建时间等Git 信息通过 `git.properties` 文件注入到 Spring 容器中
* </p>
*
* <p>此外该类支持在项目启动时通过日志记录版本信息方便追踪和调试
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Getter
@Setter
@Component
@Slf4j
@PropertySource("classpath:git.properties")
public class ProjectGitVersionInfo {
/**
* Git 提交 ID
*/
@Value("${git.commit.id}")
private String commitId;
/**
* Git 提交描述信息
*/
@Value("${git.commit.id.describe}")
private String commitDescribe;
/**
* Git 提交时间
*/
@Value("${git.commit.time}")
private String commitTime;
/**
* Git 最近的标签名称
*/
@Value("${git.closest.tag.name}")
private String tagName;
/**
* 构建时间
*/
@Value("${git.build.time}")
private String buildTime;
/**
* 当前 Git 分支
*/
@Value("${git.branch}")
private String gitBranch;
/**
* 初始化全局版本信息
*
* <p>该方法在 Spring 容器完成依赖注入后自动调用用于记录版本信息到日志中
* 通过组合构建时间提交 IDGit 分支和标签名称生成完整的版本信息
* </p>
*/
@PostConstruct
private void initGlobalValue() {
// 组合版本信息
String version = buildTime + " " + commitId + " " + gitBranch + " " + tagName;
// 记录版本信息到日志
log.info("Version: {}", version.trim());
}
}

View File

@ -0,0 +1,112 @@
package com.cmhi.magent.config;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
/**
* 协议配置类
*
* <p>该类用于加载 `protocol` 前缀下的配置信息并将部分配置值同步到全局静态变量中
* 配置内容包括时间戳校验开关超时时间加密类型和加密密钥等内容
* </p>
*
* <p>类中包含一个自动初始化方法用于在 Spring 容器构建完成后将配置值加载并初始化全局静态变量
* 从而方便在应用的其他地方直接访问协议相关的全局配置
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@ConfigurationProperties(prefix = "protocol")
@Configuration
@Slf4j
@SuppressWarnings({"java:S3008", "java:S1444", "java:S1104"})
public class ProtocolConfigure {
/**
* 是否校验时间戳的全局标志位
*/
public static boolean CHECK_TIMESTAMP;
/**
* 请求的全局超时时间单位
*/
public static long TIMEOUT_OF_SECONDS;
/**
* 安全协议类型的全局标志位
*/
public static int SECURITY_PROTOCOL_TYPE;
/**
* 是否校验时间戳
*/
private Boolean checkTimestamp;
/**
* 请求的超时时间单位
*/
private Integer timeoutOfSeconds;
/**
* 加密类型
*/
private Integer cryptoType;
/**
* 加密密钥
*/
private String cryptoKey;
/**
* 设置全局静态变量的值
*
* <p>通过将配置值赋值给静态变量便于应用中其他非 Spring 管理的类或组件访问这些全局配置信息
* </p>
*
* @param chkTimeStamp 是否校验时间戳
* @param timeOfSecond 超时时间单位
* @param secProType 安全协议类型
*/
private static void setGlobalVars(boolean chkTimeStamp, long timeOfSecond, int secProType) {
ProtocolConfigure.CHECK_TIMESTAMP = chkTimeStamp;
ProtocolConfigure.TIMEOUT_OF_SECONDS = timeOfSecond;
ProtocolConfigure.SECURITY_PROTOCOL_TYPE = secProType;
}
/**
* 初始化全局静态变量
*
* <p>该方法在 Spring 容器构建完成后自动调用用于从配置中加载值并初始化全局静态变量
* 如果某些配置值为空则提供默认值时间戳校验默认为 `false`超时时间默认为 `30`
* 加密类型默认为 `0`
* </p>
*/
@PostConstruct
private void initGlobalValue() {
log.info("Current: checkTimestamp = {}, timeoutOfSeconds = {}", checkTimestamp, timeoutOfSeconds);
setGlobalVars(Optional.ofNullable(checkTimestamp).orElse(false), Optional.ofNullable(timeoutOfSeconds).orElse(30),
Optional.ofNullable(cryptoType).orElse(0));
}
/**
* 获取超时时间单位毫秒
*
* <p>通过将配置的超时时间从秒转换为毫秒便于需要毫秒级时间的逻辑使用
* 如果超时时间未配置默认为 `30000` 毫秒 30
* </p>
*
* @return 超时时间单位毫秒
*/
public Long getTimeoutOfMillisecond() {
return (long) (timeoutOfSeconds * 1000);
}
}

View File

@ -0,0 +1,97 @@
package com.cmhi.magent.controller;
import com.cmhi.magent.annotation.DecryptionProtocol;
import com.cmhi.magent.annotation.EncryptionProtocol;
import com.cmhi.magent.annotation.OperationLogAnnotation;
import com.cmhi.magent.config.ProjectGitVersionInfo;
import com.cmhi.magent.pojo.po.VersionInfo;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.cmhi.magent.pojo.vo.VersionResp;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 通用接口控制器
*
* <p>该控制器包含通用的系统接口例如获取版本信息接口
* 支持加密与解密协议并记录操作日志</p>
*
* <p>接口描述</p>
* <ul>
* <li>POST /api/version获取系统的版本信息带加解密支持</li>
* <li>GET /api/version获取系统版本信息的另一个版本示例</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@RestController
@Slf4j
@Tag(name = "通用接口")
@RequestMapping(value = "/api")
public class CommonFrameworkApi {
/**
* 项目版本信息配置
*
* <p>{@link ProjectGitVersionInfo} 提供了系统版本信息的配置数据
* 包括 Git 提交 ID提交描述构建时间分支名称等</p>
*/
@Resource
private ProjectGitVersionInfo prgVer;
/**
* 获取系统版本信息POST 请求
*
* <p>该接口返回系统的版本信息包括提交 ID描述时间标签名称构建时间以及分支名称
* 接口支持加密和解密协议并记录操作日志</p>
*
* @return 封装了版本信息的响应对象
*/
@EncryptionProtocol
@DecryptionProtocol
@PostMapping("/version")
@OperationLogAnnotation(OperationModule = "SYSLOG_MOD_COMMON",
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_VERSION")
public ProtocolResp<VersionResp> getVersion() {
return ProtocolResp.result(VersionResp.builder().version(VersionInfo.builder()
.commitId(prgVer.getCommitId())
.commitDescribe(prgVer.getCommitDescribe())
.commitTime(prgVer.getCommitTime())
.tagName(prgVer.getTagName())
.buildTime(prgVer.getBuildTime())
.gitBranch(prgVer.getGitBranch())
.build()).build());
}
/**
* 获取系统版本信息GET 请求
*
* <p>该接口提供系统版本信息 POST 接口返回的数据相同
* 支持加密协议和操作日志记录</p>
*
* @return 封装了版本信息的响应对象
*/
@GetMapping("/version")
@EncryptionProtocol
@OperationLogAnnotation(OperationModule = "SYSLOG_MOD_COMMON",
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_VERSION")
public ProtocolResp<VersionResp> getVersionV2() {
return ProtocolResp.result(VersionResp.builder().version(VersionInfo.builder()
.commitId(prgVer.getCommitId())
.commitDescribe(prgVer.getCommitDescribe())
.commitTime(prgVer.getCommitTime())
.tagName(prgVer.getTagName())
.buildTime(prgVer.getBuildTime())
.gitBranch(prgVer.getGitBranch())
.build()).build());
}
}

View File

@ -0,0 +1,119 @@
package com.cmhi.magent.controller;
import com.cmhi.magent.annotation.EncryptionProtocol;
import com.cmhi.magent.annotation.OperationLogAnnotation;
import com.cmhi.magent.pojo.vo.GetHwInfoResp;
import com.cmhi.magent.pojo.vo.GetOsInfoResp;
import com.cmhi.magent.pojo.vo.GetProcessorInfoResp;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.cmhi.magent.service.SystemInfoService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.sql.Timestamp;
/**
* 系统信息接口控制器
*
* <p>该控制器包含 3 个接口用于获取操作系统信息处理器信息和硬件信息
* 所有接口均支持加密协议通过 {@code @EncryptionProtocol} 注解以及操作日志记录通过 {@code @OperationLogAnnotation} 注解</p>
*
* <p>接口描述</p>
* <ul>
* <li>GET /osInfo获取操作系统的相关信息</li>
* <li>GET /processorInfo获取处理器的相关信息</li>
* <li>GET /hwInfo获取硬件的相关信息</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@RestController
@Slf4j
@Tag(name = "系统信息接口")
@RequestMapping(value = "/api/systemInfo")
public class SystemInfoApi {
/**
* 系统信息服务类
*
* <p>通过该服务类调用底层逻辑获取操作系统信息处理器信息和硬件信息</p>
*/
@Resource
private SystemInfoService systemInfoService;
/**
* 获取操作系统信息
*
* <p>该接口返回操作系统名称以及系统启动时间返回数据通过加密协议进行保护
* 并记录操作日志</p>
*
* @return 封装了操作系统信息的响应对象
*/
@GetMapping("/osInfo")
@EncryptionProtocol
@OperationLogAnnotation(
OperationModule = "SYSLOG_MOD_SYSINFO",
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_OS_INFO"
)
public ProtocolResp<GetOsInfoResp> getOsInfo() {
return ProtocolResp.result(
GetOsInfoResp.builder()
.os(systemInfoService.getOsName())
.bootTime(new Timestamp(systemInfoService.getOsBootTimeStamp()))
.build()
);
}
/**
* 获取处理器信息
*
* <p>该接口返回处理器的相关信息 CPU 详情返回数据通过加密协议进行保护
* 并记录操作日志</p>
*
* @return 封装了处理器信息的响应对象
*/
@GetMapping("/processorInfo")
@EncryptionProtocol
@OperationLogAnnotation(
OperationModule = "SYSLOG_MOD_SYSINFO",
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_CPU_INFO"
)
public ProtocolResp<GetProcessorInfoResp> getProcessorInfo() {
return ProtocolResp.result(
GetProcessorInfoResp.builder()
.cpuInfo(systemInfoService.getProcessInfo())
.build()
);
}
/**
* 获取硬件信息
*
* <p>该接口返回系统硬件的相关信息返回数据通过加密协议进行保护
* 并记录操作日志</p>
*
* @return 封装了硬件信息的响应对象
*/
@GetMapping("/hwInfo")
@EncryptionProtocol
@OperationLogAnnotation(
OperationModule = "SYSLOG_MOD_SYSINFO",
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_HW_INFO"
)
public ProtocolResp<GetHwInfoResp> getHwInfo() {
return ProtocolResp.result(
GetHwInfoResp.builder()
.hwInfo(systemInfoService.getHardwareInfo())
.build()
);
}
}

View File

@ -0,0 +1,76 @@
package com.cmhi.magent.crypto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* 解密请求协议类
*
* <p>该类实现了 {@link HttpInputMessage} 接口用于对传入的 HTTP 请求消息进行解密处理</p>
*
* <p>解密后的内容可通过 {@link #getBody()} 方法获取同时保留了原始的请求头</p>
*
* <p>注意该类依赖于 Apache Commons IO Lombok </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Slf4j
public class DecryptRequestProtocol implements HttpInputMessage {
/**
* 原始的 HTTP 输入消息
*/
private final HttpInputMessage inputMessage;
/**
* 解密后的消息内容
*/
private final String msgContent;
/**
* 构造方法
*
* @param inputMessage 原始的 {@link HttpInputMessage} 对象
* @param msgContent 解密后的消息内容
* @since 2025-01-07
*/
@Contract(pure = true)
public DecryptRequestProtocol(HttpInputMessage inputMessage, String msgContent) {
this.inputMessage = inputMessage;
this.msgContent = msgContent;
}
/**
* 获取解密后的请求体
*
* @return 解密后的 {@link InputStream} 对象
* @since 2025-01-07
*/
@Override
@NotNull
public InputStream getBody() {
// 将解密后的消息内容转换为 InputStream 并返回
return IOUtils.toInputStream(msgContent, StandardCharsets.UTF_8);
}
/**
* 获取原始的 HTTP 请求头
*
* @return 原始的 {@link HttpHeaders} 对象
* @since 2025-01-07
*/
@Override
@NotNull
public HttpHeaders getHeaders() {
// 返回原始 HTTP 输入消息的头信息
return inputMessage.getHeaders();
}
}

View File

@ -0,0 +1,134 @@
package com.cmhi.magent.crypto;
import com.cmhi.magent.annotation.DecryptionProtocol;
import com.cmhi.magent.service.ProtocolSecurityService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* 请求协议安全处理类
*
* <p>该类实现了 {@link RequestBodyAdvice} 接口用于在读取请求体之前对其进行自定义处理</p>
*
* <p>功能</p>
* <ul>
* <li>判断是否需要对请求内容进行解密处理</li>
* <li>在读取请求体之前调用自定义解密逻辑</li>
* <li>支持空请求体和解密后的请求体的处理</li>
* </ul>
*
* <p>在使用该类时需要在控制器类或方法上添加 {@link DecryptionProtocol} 注解以启用解密功能</p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Slf4j
@RestControllerAdvice
public class RequestProtocolSecurity implements RequestBodyAdvice {
/**
* 协议安全服务用于处理解密逻辑
*/
@Resource
private ProtocolSecurityService protocolSecurityService;
/**
* 判断是否支持解密逻辑
*
* <p>当目标类或方法上存在 {@link DecryptionProtocol} 注解时启用解密功能</p>
*
* @param methodParameter 方法参数信息
* @param type 请求体的类型
* @param aClass 消息转换器的类
* @return 如果需要解密返回 true否则返回 false
* @since 2025-01-07
*/
@Override
public boolean supports(@NotNull MethodParameter methodParameter,
@NotNull Type type,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) {
// 检查类级别或方法级别上是否存在 @DecryptionProtocol 注解
return methodParameter.getContainingClass().isAnnotationPresent(DecryptionProtocol.class)
|| methodParameter.hasMethodAnnotation(DecryptionProtocol.class);
}
/**
* 在读取请求体之前执行解密逻辑
*
* <p>调用 {@link ProtocolSecurityService#decryptProtocol(HttpInputMessage)} 对请求体进行解密</p>
*
* @param httpInputMessage 原始 HTTP 请求体
* @param methodParameter 方法参数信息
* @param type 请求体的类型
* @param aClass 消息转换器的类
* @return 包含解密后数据的 {@link HttpInputMessage}
* @throws IOException 如果解密过程中发生 I/O 错误
* @since 2025-01-07
*/
@Override
@NotNull
public HttpInputMessage beforeBodyRead(@NotNull HttpInputMessage httpInputMessage,
@NotNull MethodParameter methodParameter,
@NotNull Type type,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
// 调用协议安全服务进行解密并返回解密后的 HttpInputMessage
return protocolSecurityService.decryptProtocol(httpInputMessage);
}
/**
* 处理空请求体的逻辑
*
* <p>如果请求体为空则默认直接返回空对象</p>
*
* @param o 原始请求体对象通常为 null
* @param httpInputMessage 原始 HTTP 请求体
* @param methodParameter 方法参数信息
* @param type 请求体的类型
* @param aClass 消息转换器的类
* @return 处理后的对象如果请求体为空则返回原始对象
* @since 2025-01-07
*/
@Override
public Object handleEmptyBody(Object o,
@NotNull HttpInputMessage httpInputMessage,
@NotNull MethodParameter methodParameter,
@NotNull Type type,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) {
// 对空请求体无特殊处理直接返回空对象
return o;
}
/**
* 在读取请求体之后的处理逻辑
*
* <p>读取请求体完成后直接返回解密后的对象</p>
*
* @param o 解密后的请求体对象
* @param httpInputMessage 原始 HTTP 请求体
* @param methodParameter 方法参数信息
* @param type 请求体的类型
* @param aClass 消息转换器的类
* @return 解密后的请求体对象
* @since 2025-01-07
*/
@Override
@NotNull
public Object afterBodyRead(@NotNull Object o,
@NotNull HttpInputMessage httpInputMessage,
@NotNull MethodParameter methodParameter,
@NotNull Type type,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) {
// 读取请求体完成后直接返回解密后的请求体对象
return o;
}
}

View File

@ -0,0 +1,110 @@
package com.cmhi.magent.crypto;
import com.cmhi.magent.annotation.EncryptionProtocol;
import com.cmhi.magent.common.ProtoCryptoType;
import com.cmhi.magent.config.ProtocolConfigure;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.cmhi.magent.service.ProtocolSecurityService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Objects;
/**
* 响应协议安全处理类
*
* <p>该类实现了 {@link ResponseBodyAdvice} 接口用于对响应体在返回客户端之前进行自定义处理</p>
*
* <p>功能</p>
* <ul>
* <li>判断响应是否需要加密处理</li>
* <li>根据配置动态决定加密逻辑支持无加密模式CRYPTO_NONE</li>
* <li>设置 HTTP 状态码以匹配 ProtocolResp 的响应码</li>
* </ul>
*
* <p>在使用该类时需要在控制器类或方法上添加 {@link EncryptionProtocol} 注解以启用加密功能</p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Slf4j
@RestControllerAdvice
public class ResponseProtocolSecurity implements ResponseBodyAdvice<Object> {
/**
* 协议安全服务用于处理加密逻辑
*/
@Resource
private ProtocolSecurityService protocolSecurityService;
/**
* 协议配置服务用于获取当前加密类型
*/
@Resource
private ProtocolConfigure protocolConfigure;
/**
* 判断是否支持加密逻辑
*
* <p>当目标类或方法上存在 {@link EncryptionProtocol} 注解时启用加密功能</p>
*
* @param methodParameter 方法参数信息
* @param aClass 消息转换器的类
* @return 如果需要加密返回 true否则返回 false
* @since 2025-01-07
*/
@Override
public boolean supports(@NotNull MethodParameter methodParameter,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) {
// 检查类级别或方法级别上是否存在 @EncryptionProtocol 注解
return methodParameter.getContainingClass().isAnnotationPresent(EncryptionProtocol.class)
|| methodParameter.hasMethodAnnotation(EncryptionProtocol.class);
}
/**
* 在写入响应体之前执行加密逻辑
*
* <p>根据配置的加密类型对响应体进行加密处理同时设置 HTTP 状态码</p>
*
* @param o 原始响应体对象
* @param methodParameter 方法参数信息
* @param mediaType 响应的媒体类型
* @param aClass 消息转换器的类
* @param serverHttpRequest HTTP 请求信息
* @param serverHttpResponse HTTP 响应信息
* @return 加密后的响应体对象或原始对象
* @since 2025-01-07
*/
@Override
@NotNull
public Object beforeBodyWrite(Object o,
@NotNull MethodParameter methodParameter,
@NotNull MediaType mediaType,
@NotNull Class<? extends HttpMessageConverter<?>> aClass,
@NotNull ServerHttpRequest serverHttpRequest,
@NotNull ServerHttpResponse serverHttpResponse) {
// 如果配置为 "无加密模式"直接返回原始响应
if (Objects.equals(protocolConfigure.getCryptoType(), ProtoCryptoType.CRYPTO_NONE.getValue())) {
if (o instanceof ProtocolResp) {
// 设置 HTTP 响应状态码
serverHttpResponse.setStatusCode(org.springframework.http.HttpStatus.valueOf(((ProtocolResp<?>) o).getCode()));
}
return o;
} else {
// 调用协议安全服务进行加密返回加密后的 ProtocolResp
ProtocolResp<String> rspInfo = protocolSecurityService.encryptProtocol(o, protocolConfigure.getCryptoType());
// 设置 HTTP 响应状态码
serverHttpResponse.setStatusCode(org.springframework.http.HttpStatus.valueOf(rspInfo.getCode()));
return rspInfo;
}
}
}

View File

@ -0,0 +1,240 @@
package com.cmhi.magent.crypto.arithmetic;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* 加密与解密工具类
*
* <p>提供以下功能</p>
* <ul>
* <li>Base64 的编码与解码</li>
* <li>SHA-256 MD5 哈希算法</li>
* <li>AES128 256 加密与解密</li>
* <li>DES 加密与解密</li>
* </ul>
*
* <p>注意所有方法均为静态无法实例化该类</p>
*
* @author
* huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2023-10
*/
@SuppressWarnings({"java:S5542", "java:S5547", "java:S4790"})
public class CryptoHelper {
private static final String AES_ALGORITHM_STR = "AES/ECB/PKCS5Padding";
private static final String DES_ALGORITHM_STR = "DES/ECB/PKCS5Padding";
private static final String SHA1_PRNG = "SHA1PRNG";
/**
* 私有化构造方法以防止实例化
*/
private CryptoHelper() {
throw new AssertionError("Instantiating utility class.");
}
/**
* 解码 Base64 编码的字符串
*
* @param ciphertext Base64 编码的密文
* @return 解码后的字节数组
*/
public static byte[] base64Decryption(String ciphertext) {
return Base64.getDecoder().decode(ciphertext);
}
/**
* 对字节数组进行 Base64 编码
*
* @param plaintext 明文字节数组
* @return Base64 编码后的字符串
*/
public static String base64Encryption(byte[] plaintext) {
return Base64.getEncoder().encodeToString(plaintext);
}
/**
* 使用 SHA-256 哈希算法对文本进行哈希处理
*
* @param plaintext 输入的明文字符串
* @return 哈希后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 SHA-256 算法
*/
public static byte[] sha256Encryption(String plaintext) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(plaintext.getBytes(StandardCharsets.UTF_8));
return messageDigest.digest();
}
/**
* 使用 MD5 哈希算法对文本进行哈希处理并返回 Base64 编码的结果
*
* @param plaintext 输入的明文字符串
* @return MD5 哈希后的 Base64 字符串
* @throws NoSuchAlgorithmException 如果系统不支持 MD5 算法
*/
public static String md5Encryption(String plaintext) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(plaintext.getBytes(StandardCharsets.UTF_8));
return base64Encryption(messageDigest.digest());
}
/**
* 使用 AES-128 加密字节数组
*
* @param plaintext 明文字节数组
* @param aesKey AES 密钥字符串
* @return 加密后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 AES 算法
* @throws NoSuchPaddingException 如果填充机制不可用
* @throws InvalidKeyException 如果密钥无效
* @throws BadPaddingException 如果填充无效
* @throws IllegalBlockSizeException 如果数据块大小不合法
*/
public static byte[] aes128Encryption(byte[] plaintext,
String aesKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance(SHA1_PRNG);
secureRandom.setSeed(aesKey.getBytes());
keyGen.init(128, secureRandom);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM_STR);
SecretKeySpec key = new SecretKeySpec(keyGen.generateKey().getEncoded(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plaintext);
}
/**
* 使用 AES-128 解密字节数组
*
* @param ciphertext 密文字节数组
* @param aesKey AES 密钥字符串
* @return 解密后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 AES 算法
* @throws NoSuchPaddingException 如果填充机制不可用
* @throws InvalidKeyException 如果密钥无效
* @throws BadPaddingException 如果填充无效
* @throws IllegalBlockSizeException 如果数据块大小不合法
*/
public static byte[] aes128Decryption(byte[] ciphertext,
String aesKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance(SHA1_PRNG);
secureRandom.setSeed(aesKey.getBytes());
keyGen.init(128, secureRandom);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM_STR);
SecretKeySpec key = new SecretKeySpec(keyGen.generateKey().getEncoded(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(ciphertext);
}
/**
* 使用 AES-256 加密字节数组
*
* @param plaintext 明文字节数组
* @param aesKey AES 密钥字符串
* @return 加密后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 AES 算法
* @throws NoSuchPaddingException 如果填充机制不可用
* @throws InvalidKeyException 如果密钥无效
* @throws BadPaddingException 如果填充无效
* @throws IllegalBlockSizeException 如果数据块大小不合法
*/
public static byte[] aes256Encryption(byte[] plaintext,
String aesKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM_STR);
SecretKeySpec key = new SecretKeySpec(sha256Encryption(aesKey), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plaintext);
}
/**
* 使用 AES-256 解密字节数组
*
* @param ciphertext 密文字节数组
* @param aesKey AES 密钥字符串
* @return 解密后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 AES 算法
* @throws NoSuchPaddingException 如果填充机制不可用
* @throws InvalidKeyException 如果密钥无效
* @throws BadPaddingException 如果填充无效
* @throws IllegalBlockSizeException 如果数据块大小不合法
*/
public static byte[] aes256Decryption(byte[] ciphertext,
String aesKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM_STR);
SecretKeySpec key = new SecretKeySpec(sha256Encryption(aesKey), "AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(ciphertext);
}
/**
* 使用 DES 加密字节数组
*
* @param plaintext 明文字节数组
* @param desKey DES 密钥字符串
* @return 加密后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 DES 算法
* @throws NoSuchPaddingException 如果填充机制不可用
* @throws InvalidKeyException 如果密钥无效
* @throws BadPaddingException 如果填充无效
* @throws IllegalBlockSizeException 如果数据块大小不合法
*/
public static byte[] desEncryption(byte[] plaintext,
String desKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
KeyGenerator keyGen = KeyGenerator.getInstance("DES");
SecureRandom secureRandom = SecureRandom.getInstance(SHA1_PRNG);
secureRandom.setSeed(desKey.getBytes());
keyGen.init(56, secureRandom);
Cipher cipher = Cipher.getInstance(DES_ALGORITHM_STR);
SecretKeySpec key = new SecretKeySpec(keyGen.generateKey().getEncoded(), "DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plaintext);
}
/**
* 使用 DES 解密字节数组
*
* @param ciphertext 密文字节数组
* @param desKey DES 密钥字符串
* @return 解密后的字节数组
* @throws NoSuchAlgorithmException 如果系统不支持 DES 算法
* @throws NoSuchPaddingException 如果填充机制不可用
* @throws InvalidKeyException 如果密钥无效
* @throws BadPaddingException 如果填充无效
* @throws IllegalBlockSizeException 如果数据块大小不合法
*/
public static byte[] desDecryption(byte[] ciphertext,
String desKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
KeyGenerator keyGen = KeyGenerator.getInstance("DES");
SecureRandom secureRandom = SecureRandom.getInstance(SHA1_PRNG);
secureRandom.setSeed(desKey.getBytes());
keyGen.init(56, secureRandom);
Cipher cipher = Cipher.getInstance(DES_ALGORITHM_STR);
SecretKeySpec key = new SecretKeySpec(keyGen.generateKey().getEncoded(), "DES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(ciphertext);
}
}

View File

@ -0,0 +1,75 @@
package com.cmhi.magent.exception;
import com.cmhi.magent.common.ErrorCode;
/**
* 通用错误码异常类
*
* <p>该异常类封装了错误码{@link ErrorCode}以及可选的描述信息便于在业务中统一处理异常</p>
*
* <p>开发者可以通过如下方式使用该类</p>
* <pre>
* throw new CommonErrorCodeException(ErrorCode.INVALID_PARAMETER, "Invalid input provided.");
* </pre>
*
* <p>支持以下三种构造方式</p>
* <ul>
* <li>仅通过错误码实例化异常默认使用错误码中的描述信息</li>
* <li>通过错误码和自定义描述信息实例化异常</li>
* <li>通过错误码和多条描述信息实例化异常</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@SuppressWarnings("java:S1068")
public class CommonErrorCodeException extends RuntimeException {
/**
* 错误码实例表示具体的错误类型
*/
private final ErrorCode err;
/**
* 错误描述信息可以包含单条或多条描述内容
*/
private final String[] description;
/**
* 使用错误码实例化异常
*
* <p>该构造方法会自动使用 {@link ErrorCode#getDescription()} 作为异常的描述信息</p>
*
* @param err 错误码 {@link ErrorCode}
*/
public CommonErrorCodeException(ErrorCode err) {
super(err.getDescription());
this.err = err;
this.description = new String[] {err.getDescription()};
}
/**
* 使用错误码和单条自定义描述信息实例化异常
*
* @param err 错误码 {@link ErrorCode}
* @param readme 自定义描述信息
*/
public CommonErrorCodeException(ErrorCode err, String readme) {
super(readme);
this.err = err;
this.description = new String[] {readme};
}
/**
* 使用错误码和多条描述信息实例化异常
*
* @param err 错误码 {@link ErrorCode}
* @param readme 多条自定义描述信息
*/
public CommonErrorCodeException(ErrorCode err, String[] readme) {
super(err.getDescription());
this.err = err;
this.description = readme;
}
}

View File

@ -0,0 +1,66 @@
package com.cmhi.magent.exception;
import com.cmhi.magent.common.ErrorCode;
import lombok.Getter;
import lombok.Setter;
/**
* 安全协议异常类
*
* <p>该异常用于在安全协议相关的业务逻辑中抛出特定的错误码{@link ErrorCode}
* 开发者可以通过该异常封装错误码及描述信息以便定位和处理安全相关问题</p>
*
* <p>支持两种构造方式</p>
* <ul>
* <li>仅通过错误码创建异常自动使用错误码中的默认描述信息</li>
* <li>通过错误码和自定义描述信息创建异常</li>
* </ul>
*
* 示例用法
* <pre>
* throw new SecurityProtocolException(ErrorCode.UNAUTHORIZED_ACCESS, "User is not authorized.");
* </pre>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Getter
@Setter
public class SecurityProtocolException extends RuntimeException {
/**
* 错误码实例表示具体的错误类型
*/
private final ErrorCode err;
/**
* 描述信息说明错误的具体内容
*/
private final String description;
/**
* 使用错误码实例化异常
*
* <p>该构造方法会自动使用错误码中的描述信息 {@link ErrorCode#getDescription()}</p>
*
* @param err 错误码 {@link ErrorCode}
*/
public SecurityProtocolException(ErrorCode err) {
super(err.getDescription());
this.err = err;
this.description = err.getDescription();
}
/**
* 使用错误码和自定义描述信息实例化异常
*
* @param err 错误码 {@link ErrorCode}
* @param readme 自定义描述信息
*/
public SecurityProtocolException(ErrorCode err, String readme) {
super(readme);
this.err = err;
this.description = readme;
}
}

View File

@ -0,0 +1,156 @@
package com.cmhi.magent.interceptor;
import com.cmhi.magent.common.ConstValue;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.HelperUtils;
import com.cmhi.magent.misc.ProtocolJsonUtils;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* RequestBodyCacheWrapper 是一个自定义的 HttpServletRequest 包装类
*
* <p>该类的主要功能如下</p>
* <ul>
* <li>缓存请求体Request Body以便多次读取</li>
* <li>提取请求头Headers并存储为一个键值对的 Map</li>
* <li>提供对缓存数据请求体和请求头的访问接口</li>
* </ul>
*
* <p>该类适用于需要多次读取 HTTP 请求体内容的场景例如拦截器中读取请求体进行验证或记录</p>
*
* 示例用法
* <pre>
* HttpServletRequest request = new RequestBodyCacheWrapper(originalRequest);
* String bodyContent = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
* </pre>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Getter
@Slf4j
public class RequestBodyCacheWrapper extends HttpServletRequestWrapper {
/**
* 请求的时间戳用于记录请求时间
*/
private final Long reqTimeStamp = System.currentTimeMillis();
/**
* 缓存的请求头信息存储为键值对形式
*/
private final Map<String, String> headers = new HashMap<>();
/**
* 缓存的请求体内容
*/
private String body;
/**
* 构造方法用于初始化请求体和请求头缓存
*
* @param request 原始的 {@link HttpServletRequest} 对象
* @throws IOException 如果处理输入流时发生 I/O 错误
*/
public RequestBodyCacheWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try (InputStream inputStream = request.getInputStream()) {
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
char[] charBuffer = new char[128];
int bytesRead;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (Exception e) {
log.error("RequestWrapper read error :{}", e.getMessage());
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
try {
// 使用工具类解析请求体并格式化为 JSON
Object obj = ProtocolJsonUtils.jsonGetObject(stringBuilder.toString(), Object.class);
body = HelperUtils.getJson(obj);
// 读取请求头并存储到 Map
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getHeader(name);
headers.put(name, value);
}
} catch (Exception e) {
body = "";
} finally {
// 设置请求的语言上下文和其他信息
String language = request.getHeader(ConstValue.LANGUAGE_HEAD);
ApiContextUtils.setRequestLocal(language);
ApiContextUtils.setRequestInfo(body, reqTimeStamp);
}
}
/**
* 重写 {@link HttpServletRequest#getInputStream()} 方法返回缓存的请求体输入流
*
* @return 缓存的 {@link ServletInputStream} 对象
*/
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
// 当前实现不支持异步读取
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
/**
* 重写 {@link HttpServletRequest#getReader()} 方法返回缓存的请求体 BufferedReader
*
* @return 缓存的 {@link BufferedReader} 对象
*/
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
}
}

View File

@ -0,0 +1,95 @@
package com.cmhi.magent.interceptor;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* RequestBodyFilter 是一个过滤器用于拦截 HTTP 请求并包装 {@link HttpServletRequest}
* 以支持多次读取请求体内容
*
* <p>该过滤器在请求进入应用程序之前使用 {@link RequestBodyCacheWrapper} 包装原始的
* {@link HttpServletRequest} 对象并将包装后的请求对象传递给后续的处理链</p>
*
* 功能特点
* <ul>
* <li>缓存请求体支持多次读取</li>
* <li>在大多数情况下原始的 {@link HttpServletRequest} 中的输入流InputStream只能读取一次
* 使用该过滤器后可以通过包装后的对象多次读取请求体内容</li>
* </ul>
*
* 配置说明
* <ul>
* <li>通过 {@link WebFilter} 注解配置过滤器名称和 URL 模式</li>
* <li>通过 {@link Order} 注解设置过滤器的执行顺序值越小优先级越高</li>
* </ul>
*
* 示例应用场景
* <ul>
* <li>日志记录需要记录完整的请求体内容</li>
* <li>安全校验在进入控制器之前对请求体内容进行校验或处理</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Component
@WebFilter(filterName = "RequestBodyFilter", urlPatterns = "/*")
@Order(10000)
public class RequestBodyFilter implements Filter {
/**
* 初始化过滤器方法
*
* <p>该方法在过滤器实例被容器初始化时调用通常用于资源初始化操作</p>
*
* @param filterConfig 过滤器配置对象
* @throws ServletException 如果初始化失败
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 调用父类的默认实现可选
Filter.super.init(filterConfig);
}
/**
* 核心过滤逻辑
*
* <p>该方法会拦截所有 HTTP 请求 {@link HttpServletRequest} 包装为
* {@link RequestBodyCacheWrapper}并将包装后的请求对象传递给后续的过滤链</p>
*
* @param servletRequest 原始的请求对象
* @param servletResponse 原始的响应对象
* @param filterChain 过滤器链用于将请求和响应传递给下一个过滤器或目标资源
* @throws IOException 如果处理输入流或输出流时发生 I/O 错误
* @throws ServletException 如果发生其他错误
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// 定义包装后的请求对象
ServletRequest requestWrapper = null;
// 如果请求是 HttpServletRequest 类型则使用 RequestBodyCacheWrapper 包装它
if (servletRequest instanceof HttpServletRequest httpServletRequest) {
requestWrapper = new RequestBodyCacheWrapper(httpServletRequest);
}
//获取请求中的流如何将取出来的字符串再次转换成流然后把它放入到新request对象中
// 在chain.doFiler方法中传递新的request对象
if (null == requestWrapper) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
}

View File

@ -0,0 +1,106 @@
package com.cmhi.magent.interceptor;
import com.cmhi.magent.annotation.OperationLogAnnotation;
import com.cmhi.magent.common.CommonEnumHandler;
import com.cmhi.magent.common.ErrorCode;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.cmhi.magent.service.OperationLogService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.lang.reflect.Method;
/**
* RestfulLogAspect 是一个 AOP 切面类用于记录 RESTful 控制器方法的操作日志
*
* <p>该切面通过拦截控制器中的方法调用提取相关信息如请求信息方法信息返回结果等
* 并将其交给 {@link OperationLogService} 进行日志记录或存储</p>
*
* 功能点
* <ul>
* <li>拦截控制器方法的调用</li>
* <li>记录方法的操作日志包括方法名称请求内容返回结果等</li>
* <li>支持通过 {@link OperationLogAnnotation} 注解扩展操作信息</li>
* </ul>
*
* 使用场景
* <ul>
* <li>用户操作日志记录</li>
* <li>接口访问日志记录</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Aspect
@Component
@Order(1)
@Slf4j
public class RestfulLogAspect {
@Resource
private OperationLogService optLogDbService;
/**
* 定义切点拦截所有 RESTful 控制器中的公共方法
*
* <p>切点表达式 `execution(public * com.cmhi.magent.controller.*.*(..))`
* 表示拦截 `com.cmhi.magent.controller` 包内所有类的公共方法</p>
*/
@Pointcut("execution(public * com.cmhi.magent.controller.*.*(..))")
public void restfulLog() {
// 切点方法无需实现作为标记使用
}
/**
* 在目标方法正常返回后执行操作日志记录
*
* @param joinPoint 切入点信息包含被拦截方法的上下文信息
* @param result 方法的返回结果
*/
@AfterReturning(returning = "result", value = "restfulLog()")
public void saveOperationLog(JoinPoint joinPoint, Object result) {
// 获取 RequestAttributes 对象用于获取当前请求信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return;
}
// RequestAttributes 获取 HttpServletRequest 对象
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
if (request == null) {
return;
}
// 获取被拦截的方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取具体的被调用方法
Method method = signature.getMethod();
// 获取方法上的自定义注解信息
OperationLogAnnotation annotation = method.getAnnotation(OperationLogAnnotation.class);
// 根据返回结果类型记录操作日志
if (result instanceof ProtocolResp<?> protocolResp && protocolResp.getMsgContent() instanceof BaseRespStatus respStatus) {
// 从返回结果中提取错误码
ErrorCode errorCode = CommonEnumHandler.codeOf(ErrorCode.class, respStatus.getStatus());
// 记录操作日志
optLogDbService.systemOperationLog(method.getName(), request, result, annotation, errorCode);
} else {
// 如果返回结果不符合预期记录为系统异常
optLogDbService.systemOperationLog(method.getName(), request, result, annotation, ErrorCode.ERR_SYSTEMEXCEPTION);
}
}
}

View File

@ -0,0 +1,112 @@
package com.cmhi.magent.misc;
import com.cmhi.magent.pojo.po.ApiContext;
import java.util.Locale;
/**
* ApiContextUtils 是一个线程安全的工具类用于管理当前线程的 {@link ApiContext} 对象
*
* <p>基于 ThreadLocal 的方式为每个线程维护独立的上下文信息如请求体请求时间本地化语言等
* 该类提供了静态方法来获取设置或清除线程的上下文数据</p>
*
* <p>使用场景</p>
* <ul>
* <li>存储与当前请求相关联的上下文信息</li>
* <li>在应用程序的不同模块中共享请求上下文如用户信息本地化语言等</li>
* <li>线程结束时确保调用 {@link #clear()} 方法清理上下文避免内存泄漏</li>
* </ul>
*
* <p>注意此类不可实例化应通过静态方法使用</p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class ApiContextUtils {
/**
* 私有构造方法防止实例化
*/
private ApiContextUtils() {
throw new AssertionError("Instantiating utility class.");
}
/**
* ThreadLocal 变量用于存储当前线程的 {@link ApiContext} 对象
*/
private static final ThreadLocal<ApiContext> API_CONTEXT = new ThreadLocal<>();
/**
* 获取当前线程的 {@link ApiContext} 对象如果不存在则创建一个新的实例
*
* @return 当前线程的 {@link ApiContext} 对象
*/
public static ApiContext get() {
ApiContext context = API_CONTEXT.get();
if (context == null) {
// 如果当前线程没有上下文信息则创建一个新的
context = new ApiContext();
API_CONTEXT.set(context);
}
return context;
}
/**
* 设置请求体信息和请求时间到当前线程的 {@link ApiContext} 对象
*
* @param reqBody 请求体内容
* @param reqTime 请求时间戳
*/
public static void setRequestInfo(String reqBody, Long reqTime) {
ApiContext context = get();
context.setRequestBody(reqBody);
context.setRequestTime(reqTime);
}
/**
* 获取当前线程的语言环境如果没有设置则返回系统默认语言
*
* @return 当前线程的语言环境字符串 "en", "zh"
*/
public static String getLanguage() {
ApiContext context = get();
// 如果没有设置语言则返回系统默认语言
return HelperUtils.stringNotEmptyOrNull(context.getReqLocal()) ? context.getReqLocal() : Locale.getDefault().getLanguage();
}
/**
* 设置请求的语言环境到当前线程的 {@link ApiContext} 对象
*
* @param local 语言环境字符串 "en", "zh"
*/
public static void setRequestLocal(String local) {
ApiContext context = get();
context.setReqLocal(local);
}
/**
* 设置用户相关的 JWT 信息到当前线程的 {@link ApiContext} 对象
*
* @param jwt 用户的 JWT 字符串
* @param username 用户名
* @param uid 用户唯一标识符
* @param id 用户 ID
*/
public static void setJwtUserInfo(String jwt, String username, String uid, Long id) {
ApiContext context = get();
context.setJwt(jwt);
context.setUsername(username);
context.setUid(uid);
context.setId(id);
}
/**
* 清除当前线程的 {@link ApiContext} 对象避免内存泄漏
*
* <p>该方法应在线程结束时调用尤其是在使用线程池时确保清理上下文数据</p>
*/
public static void clear() {
API_CONTEXT.remove();
}
}

View File

@ -0,0 +1,248 @@
package com.cmhi.magent.misc;
import com.cmhi.magent.config.ObjectMapperProvider;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* HelperUtils 是一个工具类提供常见的实用方法 JSON 转换字符串处理时间操作校验工具等
*
* <p>使用场景</p>
* <ul>
* <li>处理 JSON 数据</li>
* <li>解析请求头获取客户端 IP</li>
* <li>字符串截断流转换</li>
* <li>格式化时间校验对象</li>
* </ul>
*
* 注意此类不可实例化应通过静态方法使用
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class HelperUtils {
/**
* 静态 ObjectMapper 实例用于 JSON 操作
*/
private static final ObjectMapper OBJ_MAPPER = ObjectMapperProvider.getMapper();
/**
* 私有构造方法防止实例化
*/
private HelperUtils() {
throw new AssertionError("Instantiating utility class.");
}
/**
* 将对象转换为 JSON 字符串
*
* @param obj 任意对象
* @param <T> 对象的类型
* @return 转换后的 JSON 字符串
* @throws JsonProcessingException 如果 JSON 序列化失败
*/
public static <T> String getJson(T obj) throws JsonProcessingException {
return OBJ_MAPPER.writeValueAsString(obj);
}
/**
* 截断字符串到指定长度超出部分以 "..." 表示
*
* @param orgString 原始字符串
* @param maxLength 最大长度
* @return 截断后的字符串
*/
public static String truncateString(String orgString, int maxLength) {
if (!stringNotEmptyOrNull(orgString) || orgString.length() <= maxLength) {
return orgString;
} else {
return orgString.substring(0, maxLength - 4) + "...";
}
}
/**
* 将字节数组转换为十六进制字符串表示
*
* @param bArray 字节数组
* @return 十六进制字符串
*/
public static String bytesToHexString(byte[] bArray) {
StringBuilder sb = new StringBuilder(bArray.length);
for (byte b : bArray) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() < 2) {
sb.append('0');
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 将字符串转换为 GB2312 编码的字节数组
*
* @param str 原始字符串
* @return GB2312 编码的字节数组
* @throws UnsupportedEncodingException 如果编码不被支持
*/
public static byte[] convertStringToGb2312(String str) throws UnsupportedEncodingException {
return str.getBytes("GB2312");
}
/**
* 将输入流转换为字符串
*
* @param inputStream 输入流
* @return 转换后的字符串
* @throws IOException 如果读取流时发生错误
*/
public static String inputStream2String(InputStream inputStream) throws IOException {
if (inputStream == null) {
return "";
}
StringBuilder out = new StringBuilder();
try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) > 0) {
out.append(buffer, 0, charsRead);
}
}
return out.toString();
}
/**
* 将时间字符串转换为时间戳毫秒
*
* @param dateTime 时间字符串格式yyyy-MM-dd HH:mm:ss
* @return 时间戳毫秒
*/
public static long getTimestampMilliSecond(String dateTime) {
return Timestamp.valueOf(dateTime).toInstant().toEpochMilli();
}
/**
* 获取当前时间的字符串表示格式为 yyyy-MM-dd HH:mm:ss
*
* @return 当前时间的字符串表示
*/
public static String getCurrentDatetime() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
/**
* 校验对象是否符合约束条件
*
* @param t 待校验的对象
* @param groups 校验组
* @param <T> 对象类型
* @return 校验错误的消息列表如果无错误则返回空列表
*/
public static <T> List<String> validate(T t, Class<?>... groups) {
LocaleContextHolder.setLocale(Locale.forLanguageTag(ApiContextUtils.getLanguage().replace('_', '-')));
LocalValidatorFactoryBean validatorFactory = SpringBootBeanUtils.getBean(LocalValidatorFactoryBean.class);
Set<ConstraintViolation<T>> violations = (groups != null && groups.length > 0)
? validatorFactory.getValidator().validate(t, groups)
: validatorFactory.getValidator().validate(t);
List<String> errors = new ArrayList<>();
violations.forEach(violation -> errors.add(violation.getMessage()));
return errors;
}
/**
* 判断字符串是否非空且不为 null
*
* @param str 待检查的字符串
* @return 如果字符串非空且不为 null则返回 true否则返回 false
*/
public static boolean stringNotEmptyOrNull(String str) {
return str != null && !str.isEmpty();
}
/**
* 合并数据库字符串值如果字符串为空则返回 null
*
* @param str 原始字符串
* @return 如果非空返回原字符串否则返回 null
*/
public static String meagreDbStringValue(String str) {
return stringNotEmptyOrNull(str) ? str : null;
}
/**
* 获取 HTTP 请求头的 JSON 表示
*
* @param request HTTP 请求
* @return 请求头的 JSON 表示
* @throws JsonProcessingException 如果 JSON 转换失败
*/
public static String getHttpRequestHeaders(HttpServletRequest request) throws JsonProcessingException {
if (request == null) {
return "{}";
}
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
headers.put(name, request.getHeader(name));
}
return getJson(headers);
}
/**
* 获取 HTTP 请求的源 IP 地址
*
* @param request HTTP 请求
* @return IP 地址
* @throws UnknownHostException 如果无法获取本机地址
*/
public static String getHttpRequestSrcIp(HttpServletRequest request) throws UnknownHostException {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
String unknown = "unknown";
String localhostIp = "127.0.0.1";
String localhostIpv6 = "::1";
String separator = ",";
if (ip == null || ip.isEmpty() || unknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (localhostIp.equals(ip) || localhostIpv6.equals(ip)) {
InetAddress inetAddress = InetAddress.getLocalHost();
ip = inetAddress.getHostAddress();
}
if (ip != null && ip.contains(separator)) {
ip = ip.split(separator)[0];
}
return localhostIpv6.equals(ip) ? localhostIp : ip;
}
}

View File

@ -0,0 +1,203 @@
package com.cmhi.magent.misc;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Objects;
/**
* 自定义的 {@link ParameterizedType} 实现用于描述带泛型参数的类型
*
* <p>此类广泛用于序列化和反序列化场景尤其适用于 JSON 解析框架 JacksonGson
* 可以构造复杂的泛型类型例如`List<String>``Map<String, Integer>`</p>
*
* 功能特点
* <ul>
* <li>支持自定义泛型类型创建</li>
* <li>实现了 {@code ParameterizedType} 的所有方法</li>
* <li>提供友好的 `toString` 表示用于调试或日志打印</li>
* </ul>
*
* 使用场景
* <ul>
* <li>动态构造复杂的泛型类型</li>
* <li>解决类型擦除问题特别是在运行时需要保留泛型信息的场景</li>
* </ul>
*
* 示例
* <pre>{@code
* JsonParameterizedType type = JsonParameterizedType.make(
* List.class,
* new Type[]{String.class},
* null
* );
* System.out.println(type); // 输出java.util.List<java.lang.String>
* }</pre>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class JsonParameterizedType implements ParameterizedType {
/**
* 泛型参数的实际类型列表
*/
private final Type[] actualTypeArguments;
/**
* 原始类型表示泛型的主类型 `List` `Map`
*/
private final Class<?> rawType;
/**
* 拥有者类型表示该泛型所属的外部类如果存在
*/
private final Type ownerType;
/**
* 私有构造函数用于创建泛型类型
*
* @param rawType 原始类型 `List` `Map`
* @param actualTypeArguments 泛型参数的实际类型
* @param ownerType 拥有者类型可以为 null
*/
private JsonParameterizedType(Class<?> rawType, Type[] actualTypeArguments, Type ownerType) {
this.actualTypeArguments = actualTypeArguments;
this.rawType = rawType;
this.ownerType = (ownerType != null) ? ownerType : rawType.getDeclaringClass();
this.validateConstructorArguments();
}
/**
* 校验构造函数的参数是否合法
*
* <p>确保原始类型声明的泛型参数数量与传入的实际类型参数数量一致</p>
*
* @throws MalformedParameterizedTypeException 如果参数数量不匹配
*/
private void validateConstructorArguments() {
TypeVariable<?>[] typeParameters = this.rawType.getTypeParameters();
if (typeParameters.length != this.actualTypeArguments.length) {
throw new MalformedParameterizedTypeException();
}
}
/**
* 获取泛型参数的实际类型列表
*
* @return 泛型参数的实际类型数组
*/
@Override
public Type @NotNull [] getActualTypeArguments() {
// 返回副本保证安全性
return this.actualTypeArguments.clone();
}
/**
* 获取原始类型未带泛型参数的主类型
*
* @return 原始类型
*/
@Override
public @NotNull Type getRawType() {
return this.rawType;
}
/**
* 获取拥有者类型外部类的类型
*
* @return 拥有者类型如果无外部类则返回 null
*/
@Override
public Type getOwnerType() {
return this.ownerType;
}
/**
* 判断该泛型类型是否与另一个泛型类型相等
*
* @param obj 要比较的对象
* @return 如果两者相等则返回 true否则返回 false
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof ParameterizedType other) {
if (this == other) {
return true;
}
return Objects.equals(this.ownerType, other.getOwnerType())
&& Objects.equals(this.rawType, other.getRawType())
&& Arrays.equals(this.actualTypeArguments, other.getActualTypeArguments());
}
return false;
}
/**
* 计算当前泛型类型的哈希值
*
* @return 哈希值
*/
@Override
public int hashCode() {
return Arrays.hashCode(this.actualTypeArguments)
^ Objects.hashCode(this.ownerType)
^ Objects.hashCode(this.rawType);
}
/**
* 返回该泛型类型的字符串表示调试友好
*
* @return 字符串表示形式
*/
@Override
@SuppressWarnings("java:S3740")
public String toString() {
StringBuilder sb = new StringBuilder();
if (this.ownerType != null) {
if (this.ownerType instanceof Class<?> ownerClass) {
sb.append(ownerClass.getName());
} else {
sb.append(this.ownerType);
}
sb.append("$");
if (this.ownerType instanceof JsonParameterizedType jsonType) {
sb.append(this.rawType.getName().replace(jsonType.rawType.getName() + "$", ""));
} else {
sb.append(this.rawType.getSimpleName());
}
} else {
sb.append(this.rawType.getName());
}
if (this.actualTypeArguments != null && this.actualTypeArguments.length > 0) {
sb.append("<");
boolean first = true;
for (Type typeArg : this.actualTypeArguments) {
if (!first) {
sb.append(", ");
}
sb.append(typeArg.getTypeName());
first = false;
}
sb.append(">");
}
return sb.toString();
}
/**
* 静态工厂方法用于创建 {@link JsonParameterizedType} 实例
*
* @param rawType 原始类型 `List` `Map`
* @param actualTypeArguments 泛型参数的实际类型
* @param ownerType 拥有者类型可以为 null
* @return 构造的 {@code JsonParameterizedType} 实例
*/
public static JsonParameterizedType make(Class<?> rawType, Type[] actualTypeArguments, Type ownerType) {
return new JsonParameterizedType(rawType, actualTypeArguments, ownerType);
}
}

View File

@ -0,0 +1,141 @@
package com.cmhi.magent.misc;
import io.micrometer.common.util.StringUtils;
import org.springframework.context.MessageSource;
import java.util.Locale;
/**
* 国际化消息工具类用于根据指定的语言或区域获取对应的消息内容
*
* <p>此类基于 Spring {@link MessageSource}提供了简化的消息获取方法
* 它支持通过指定语言代码 "en_US"动态获取对应语言的消息同时
* 支持参数化的消息内容</p>
*
* 功能特点
* <ul>
* <li>支持基于语言代码的消息解析</li>
* <li>支持默认语言应用程序配置的默认 {@code Locale}</li>
* <li>支持带参数的消息格式化</li>
* <li>使用懒加载模式获取 {@code MessageSource}避免过早初始化</li>
* </ul>
*
* 使用示例
* <pre>{@code
* // 获取英文消息
* String message = MessageUtil.get("welcome.message", "en_US");
*
* // 获取带参数的中文消息
* String message = MessageUtil.get("welcome.message", new Object[]{"John"}, "zh_CN");
* }</pre>
*
* 注意此类不可实例化应通过静态方法使用
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class MessageUtil {
/**
* 语言代码的分割大小例如 "en_US" 应被分割为 ["en", "US"]
*/
private static final int ARRAY_SIZE = 2;
/**
* 私有构造方法防止工具类被实例化
*/
private MessageUtil() {
throw new AssertionError("Instantiating utility class.");
}
/**
* 根据指定的键和语言代码获取国际化消息
* 如果语言代码无效将使用默认语言
*
* @param key 消息的键
* @param language 语言代码 "en_US" "zh_CN"
* @return 对应语言的消息如果未找到消息则返回键本身
*/
public static String get(String key, String language) {
if (!StringUtils.isEmpty(language)) {
String[] parts = language.split("_");
if (parts.length == ARRAY_SIZE) {
return get(key, new Locale(parts[0], parts[1]));
}
}
// 使用默认的国际化语言
return get(key, Locale.getDefault());
}
/**
* 根据指定的键参数和语言代码获取国际化消息
* 如果语言代码无效将使用默认语言
*
* @param key 消息的键
* @param params 消息中的参数
* @param language 语言代码 "en_US" "zh_CN"
* @return 格式化后的语言消息如果未找到消息则返回键本身
*/
public static String get(String key, Object[] params, String language) {
if (!StringUtils.isEmpty(language)) {
String[] parts = language.split("_");
if (parts.length == ARRAY_SIZE) {
return get(key, params, new Locale(parts[0], parts[1]));
}
}
return get(key, params, Locale.getDefault());
}
/**
* 根据指定的键和语言获取国际化消息
*
* @param key 消息的键
* @param language 语言{@link Locale}
* @return 对应语言的消息如果未找到消息则返回键本身
*/
private static String get(String key, Locale language) {
return get(key, new Object[0], language);
}
/**
* 根据指定的键参数和语言获取国际化消息
*
* @param key 消息的键
* @param params 消息中的参数
* @param language 语言{@link Locale}
* @return 格式化后的语言消息如果未找到消息则返回键本身
*/
private static String get(String key, Object[] params, Locale language) {
// MessageSource 获取消息
String message = getInstance().getMessage(key, params, language);
// 如果消息不为空直接返回
if (HelperUtils.stringNotEmptyOrNull(message)) {
return message;
}
// 如果消息不存在返回原始键避免返回空值
return key;
}
/**
* 获取 {@link MessageSource} 的实例
* <p>使用懒加载模式确保在需要时才初始化</p>
*
* @return {@link MessageSource} 实例
*/
private static MessageSource getInstance() {
return LazyHolder.MESSAGE_SOURCE;
}
/**
* 懒加载的内部类用于安全地初始化 {@link MessageSource}
*/
private static class LazyHolder {
/**
* Spring 容器中获取 {@link MessageSource} 实例
*/
private static final MessageSource MESSAGE_SOURCE = SpringBootBeanUtils.getBean(MessageSource.class);
}
}

View File

@ -0,0 +1,209 @@
package com.cmhi.magent.misc;
import com.cmhi.magent.config.ObjectMapperProvider;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletInputStream;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Stack;
/**
* JSON 协议工具类用于处理 JSON 数据的序列化和反序列化
*
* <p>支持通用的 JSON 对象转换以及针对 {@link ProtocolResp} 协议响应的数据结构处理
* 工具类基于 Jackson 实现确保高效解析和生成 JSON 数据</p>
*
* 功能特点
* <ul>
* <li>支持对象到字节数组的 JSON 序列化</li>
* <li>支持从字符串或输入流中反序列化 JSON 对象</li>
* <li>支持动态泛型类型的 JSON 解析解决运行时泛型丢失问题</li>
* </ul>
*
* 注意此工具类不可实例化所有方法均为静态方法
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.1.0
* @since 2025-01-07
*/
public class ProtocolJsonUtils {
/**
* 私有构造方法防止工具类被实例化
*/
private ProtocolJsonUtils() {
throw new AssertionError("Instantiating utility class.");
}
/**
* Jackson ObjectMapper 实例用于 JSON 解析和生成
*/
private static final ObjectMapper OBJ_MAPPER = ObjectMapperProvider.getMapper();
/**
* 序列化对象为 JSON 字节数组支持自定义字符集
*
* @param obj 要序列化的对象
* @param charset 字符集
* @param <T> 对象的类型
* @return JSON 字节数组
* @throws JsonProcessingException 如果序列化失败
*/
public static <T> byte[] getJsonBytes(T obj, Charset charset) throws JsonProcessingException {
return OBJ_MAPPER.writeValueAsString(obj).getBytes(charset);
}
/**
* 序列化对象为 JSON 字节数组使用默认 UTF-8 编码
*
* @param obj 要序列化的对象
* @param <T> 对象的类型
* @return JSON 字节数组
* @throws JsonProcessingException 如果序列化失败
*/
public static <T> byte[] getJsonBytes(T obj) throws JsonProcessingException {
return getJsonBytes(obj, StandardCharsets.UTF_8);
}
/**
* JSON 字符串反序列化为指定类型的对象
*
* @param json JSON 字符串
* @param valueType 要转换的目标类型
* @param <T> 目标类型
* @return 目标对象
* @throws JsonProcessingException 如果反序列化失败
*/
public static <T> T jsonGetObject(String json, Class<T> valueType) throws JsonProcessingException {
return OBJ_MAPPER.readValue(json, valueType);
}
/**
* 从输入流反序列化为泛型类型的对象
*
* @param is 输入流
* @param valueTypeRef 泛型类型引用
* @param <T> 泛型类型
* @return 目标对象
* @throws IOException 如果读取流或反序列化失败
*/
public static <T> T jsonGetObject(ServletInputStream is, TypeReference<T> valueTypeRef) throws IOException {
return OBJ_MAPPER.readValue(HelperUtils.inputStream2String(is), valueTypeRef);
}
/**
* JSON 字符串反序列化为泛型类型的对象
*
* @param json JSON 字符串
* @param valueTypeRef 泛型类型引用
* @param <T> 泛型类型
* @return 目标对象
* @throws IOException 如果反序列化失败
*/
public static <T> T jsonGetObject(String json, TypeReference<T> valueTypeRef) throws IOException {
return OBJ_MAPPER.readValue(json, valueTypeRef);
}
/**
* 创建一个动态泛型的响应类型
*
* @param c 目标泛型类型
* @return 动态泛型类型
*/
private static <T> Type createRespType(Class<T> c) {
return new ParameterizedType() {
@Override
public Type @NotNull [] getActualTypeArguments() {
return new Type[] {c};
}
@Override
public @NotNull Type getRawType() {
return ProtocolResp.class;
}
@Override
public Type getOwnerType() {
return null;
}
};
}
/**
* JSON 字符串反序列化为 {@link ProtocolResp} 对象
* 泛型类型通过指定的子类参数动态生成
*
* @param jsonString JSON 字符串
* @param subClass 响应数据的目标类型
* @param <T> 响应数据的泛型类型
* @return ProtocolResp 对象
* @throws JsonProcessingException 如果反序列化失败
*/
@SuppressWarnings("java:S1452")
public static <T> ProtocolResp<? extends BaseRespStatus> jsonGetProtocolResp(String jsonString,
Class<T> subClass) throws JsonProcessingException {
return OBJ_MAPPER.readValue(jsonString, new TypeReference<>() {
@Override
public Type getType() {
return createRespType(subClass);
}
});
}
/**
* 创建一个多级嵌套泛型的响应类型
*
* @param c 泛型类型数组
* @return 动态嵌套泛型类型
*/
@SuppressWarnings("java:S1149")
private static Type createRespType(Class<?>[] c) {
if (c.length == 1) {
return createRespType(c[0]);
}
Stack<Class<?>> stack = new Stack<>();
Arrays.stream(c).forEach(stack::push);
Class<?> first = stack.pop();
Class<?> second = stack.pop();
JsonParameterizedType type = JsonParameterizedType.make(second, new Type[] {first}, second.getDeclaringClass());
while (!stack.isEmpty()) {
Class<?> current = stack.pop();
type = JsonParameterizedType.make(current, new Type[] {type}, current.getDeclaringClass());
}
return JsonParameterizedType.make(ProtocolResp.class, new Type[] {type}, ProtocolResp.class.getDeclaringClass());
}
/**
* JSON 字符串反序列化为多级嵌套泛型的 {@link ProtocolResp} 对象
*
* @param jsonString JSON 字符串
* @param subClass 嵌套类型数组
* @return ProtocolResp 对象
* @throws JsonProcessingException 如果反序列化失败
*/
@SuppressWarnings("java:S1452")
public static ProtocolResp<? extends BaseRespStatus> jsonGetProtocolResp(String jsonString,
Class<?>[] subClass) throws JsonProcessingException {
return OBJ_MAPPER.readValue(jsonString, new TypeReference<>() {
@Override
public Type getType() {
return createRespType(subClass);
}
});
}
}

View File

@ -0,0 +1,108 @@
package com.cmhi.magent.misc;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Spring 容器工具类用于在非 Spring 管理的类中获取 Bean 实例
*
* <p>通过实现 {@link ApplicationContextAware} 接口获取 Spring 容器的 {@link ApplicationContext}
* 并将其保存为静态变量便于其他类在任何地方访问 Spring 容器</p>
*
* <p>功能特点</p>
* <ul>
* <li>支持通过 Bean 名称获取 Bean 实例</li>
* <li>支持通过 Bean 类型获取 Bean 实例</li>
* <li>支持通过 Bean 名称和类型同时获取 Bean 实例</li>
* </ul>
*
* 使用场景
* <ul>
* <li>在非 Spring 管理的类中如工具类线程第三方库回调方法需要访问 Spring 容器中的 Bean</li>
* <li>简化代码中对 Spring 容器的 Bean 获取操作</li>
* </ul>
*
* <p><strong>注意事项</strong>此工具类需要在 Spring 容器初始化时正确加载</p>
*
* 示例
* <pre>{@code
* // 获取名为 "myService" Bean
* MyService myService = (MyService) SpringBootBeanUtils.getBean("myService");
*
* // 获取类型为 MyService Bean
* MyService myService = SpringBootBeanUtils.getBean(MyService.class);
*
* // 获取名为 "myService" 且类型为 MyService Bean
* MyService myService = SpringBootBeanUtils.getBean("myService", MyService.class);
* }</pre>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Component
public class SpringBootBeanUtils implements ApplicationContextAware {
/**
* Spring 应用上下文用于获取 Spring 容器中的 Bean
* 使用 {@code @Getter} {@code @Setter} 提供线程安全的访问和修改方法
*/
@Getter
@Setter
private static ApplicationContext appContext;
/**
* 根据 Bean 名称获取 Bean 实例
*
* @param name Bean 的名称
* @return Bean 实例
* @throws BeansException 如果无法通过名称获取 Bean
*/
public static Object getBean(String name) {
return getAppContext().getBean(name);
}
/**
* 根据 Bean 类型获取 Bean 实例
*
* @param clazz Bean 的类型
* @param <T> Bean 的类型参数
* @return Bean 实例
* @throws BeansException 如果无法通过类型获取 Bean
*/
public static <T> T getBean(Class<T> clazz) {
return getAppContext().getBean(clazz);
}
/**
* 根据 Bean 名称和类型获取 Bean 实例
*
* @param name Bean 的名称
* @param clazz Bean 的类型
* @param <T> Bean 的类型参数
* @return Bean 实例
* @throws BeansException 如果无法通过名称和类型获取 Bean
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getAppContext().getBean(name, clazz);
}
/**
* 设置 Spring 应用上下文
*
* <p>此方法由 Spring 容器调用通过 {@link ApplicationContextAware} 接口注入应用上下文</p>
*
* @param applicationContext Spring 应用上下文
* @throws BeansException 如果上下文注入失败
*/
@Override
public void setApplicationContext(
@NotNull @org.jetbrains.annotations.NotNull ApplicationContext applicationContext) throws BeansException {
setAppContext(applicationContext);
}
}

View File

@ -0,0 +1,184 @@
package com.cmhi.magent.pojo.base;
import com.cmhi.magent.validation.group.ValidGroups;
import com.cmhi.magent.validation.valids.ValidProtocolTimestamp;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;
import java.io.Serial;
import java.io.Serializable;
/**
* 通用协议基类 {@code BaseProtocol}用于封装协议的基本结构
* <p>
* 该类是一个泛型类支持通过泛型参数 {@code T} 指定消息内容的类型它包含了协议的通用字段
* 如协议版本号加密类型时间戳和具体消息内容并通过 Jakarta Bean Validation 提供字段校验支持
* 确保协议数据的完整性和有效性
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>支持协议版本号加密类型和时间戳等通用字段的定义</li>
* <li>提供字段的校验规则包括非空校验范围校验和自定义校验</li>
* <li>支持通过泛型 {@code T} 动态指定消息内容的类型</li>
* <li>支持 JSON 序列化/反序列化通过 Jackson 注解配置字段顺序和忽略规则</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>适用于需要标准化协议格式的场景如网络通信中的消息封装</li>
* <li>适用于需要对协议字段进行校验的场景确保数据合法性</li>
* <li>适用于需要通过泛型动态定义消息内容结构的场景</li>
* </ul>
* </p>
*
* <p>
* 使用示例
* <pre>
* BaseProtocol<MyMessage> protocol = new BaseProtocol<>();
* protocol.setVer(1);
* protocol.setCryptoType(0);
* protocol.setTimeStamp(System.currentTimeMillis());
* protocol.setMsgContent(new MyMessage());
* </pre>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "ver": 1,
* "cryptoType": 0,
* "timeStamp": 1672531199000,
* "msgContent": {
* // 消息内容根据 T 类型决定具体结构
* }
* }
* </pre>
* </p>
*
* @param <T> 消息内容的类型由具体业务决定
* @see jakarta.validation.constraints.NotNull
* @see org.hibernate.validator.constraints.Range
* @see com.cmhi.magent.validation.valids.ValidProtocolTimestamp
* @see com.fasterxml.jackson.annotation.JsonPropertyOrder
* @see com.fasterxml.jackson.annotation.JsonInclude
* @see lombok.Data
* @see lombok.NoArgsConstructor
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@NoArgsConstructor
@JsonPropertyOrder({"ver", "cryptoType", "timeStamp", "msgContent"})
@JsonIgnoreProperties
@JsonInclude(JsonInclude.Include.NON_NULL)
@SuppressWarnings("java:S1948")
public class BaseProtocol<T> implements Serializable {
/**
* 序列化唯一标识符
*/
@Serial
private static final long serialVersionUID = -32326523643641L;
/**
* 协议版本号
* <p>
* 必填字段定义协议的版本号用于兼容性控制
* </p>
* <p>
* 校验规则
* <ul>
* <li>非空校验必须提供版本号</li>
* <li>值范围校验1 9999</li>
* </ul>
* </p>
* <p>
* 示例
* <pre>
* protocol.setVer(1);
* </pre>
* </p>
*/
@NotNull(message = "ver {item.not_null}", groups = ValidGroups.ProtocolCommonValid.class)
@Range(min = 1, max = 9999, message = "ver {item.value_range}", groups = ValidGroups.ProtocolCommonValid.class)
private Integer ver;
/**
* 加密类型
* <p>
* 必填字段定义协议使用的加密方式
* </p>
* <p>
* 校验规则
* <ul>
* <li>非空校验必须提供加密类型</li>
* <li>值范围校验0 4</li>
* </ul>
* </p>
* <p>
* 示例
* <pre>
* protocol.setCryptoType(0); // 未加密
* protocol.setCryptoType(1); // AES 加密
* </pre>
* </p>
*/
@NotNull(message = "cryptoType {item.not_null}", groups = ValidGroups.ProtocolCommonValid.class)
@Range(min = 0, max = 4, message = "cryptoType {item.value_range}", groups = ValidGroups.ProtocolCommonValid.class)
private Integer cryptoType;
/**
* 时间戳
* <p>
* 必填字段定义协议的时间戳用于记录消息的发送时间
* </p>
* <p>
* 校验规则
* <ul>
* <li>非空校验必须提供时间戳</li>
* <li>自定义校验使用 {@link ValidProtocolTimestamp} 验证时间戳的合法性</li>
* </ul>
* </p>
* <p>
* 示例
* <pre>
* protocol.setTimeStamp(System.currentTimeMillis());
* </pre>
* </p>
*/
@NotNull(message = "timeStamp {item.not_null}", groups = ValidGroups.ProtocolCommonValid.class)
@ValidProtocolTimestamp(message = "timeStamp {timestamp.timeout}", groups = ValidGroups.ProtocolCommonValid.class)
private Long timeStamp;
/**
* 消息内容
* <p>
* 可选字段表示具体的消息内容由泛型参数 {@code T} 决定其类型
* </p>
* <p>
* 校验规则
* <ul>
* <li>嵌套校验 {@code msgContent} 内容进行校验如果存在</li>
* </ul>
* </p>
* <p>
* 示例
* <pre>
* protocol.setMsgContent(new MyMessage());
* </pre>
* </p>
*/
@Valid
private T msgContent;
}

View File

@ -0,0 +1,142 @@
package com.cmhi.magent.pojo.dto;
import com.cmhi.magent.validation.group.ValidGroups;
import com.cmhi.magent.validation.valids.ValidPageSize;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;
/**
* 基础分页请求类 {@code BasePagedReq}用于封装分页请求的基础参数
* <p>
* 该类提供了常用的分页参数包括分页号pageNumber分页大小pageSize以及数据总数totalSize
* 支持字段的校验规则确保分页参数的合法性同时通过 Swagger 注解生成 OpenAPI 文档
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>支持分页请求的通用参数封装</li>
* <li>为分页字段提供校验规则如分页号的最小值校验分页大小的范围校验等</li>
* <li>支持通过 Swagger 提供字段的描述信息用于自动生成接口文档</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>适用于需要分页功能的 API 请求例如列表数据查询接口</li>
* <li>适用于需要对分页参数进行校验的场景确保请求参数合法性</li>
* </ul>
* </p>
*
* <p>
* 使用示例
* <pre>
* BasePagedReq pagedReq = new BasePagedReq();
* pagedReq.setPageNumber(1L);
* pagedReq.setPageSize(20L);
* pagedReq.setTotalSize(100L);
* </pre>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "pageNumber": 1,
* "pageSize": 20,
* "totalSize": 100
* }
* </pre>
* </p>
*
* @see jakarta.validation.constraints.NotNull
* @see jakarta.validation.constraints.Min
* @see org.hibernate.validator.constraints.Range
* @see com.cmhi.magent.validation.valids.ValidPageSize
* @see io.swagger.v3.oas.annotations.media.Schema
* @see lombok.Data
* @see lombok.NoArgsConstructor
* @see lombok.AllArgsConstructor
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "BasePagedReq", description = "分页请求基础参数")
public class BasePagedReq {
/**
* 分页号
* <p>
* 必填字段用于指定当前请求的分页页码最小值为 1
* </p>
* <p>
* 校验规则
* <ul>
* <li>非空校验分页号不能为空</li>
* <li>最小值校验分页号必须大于或等于 1</li>
* </ul>
* </p>
* <p>
* 示例
* <pre>
* pagedReq.setPageNumber(1L);
* </pre>
* </p>
*/
@Schema(description = "分页号最小值为1")
@NotNull(message = "pageNumber {item.not_null}", groups = ValidGroups.BasePagedReqValid.class)
@Min(value = 1, message = "pageNumber 最小值为1", groups = ValidGroups.BasePagedReqValid.class)
private Long pageNumber;
/**
* 分页大小
* <p>
* 必填字段用于指定每页返回的数据条数最小值为 5最大值为 100且必须为 5 的整数倍
* </p>
* <p>
* 校验规则
* <ul>
* <li>非空校验分页大小不能为空</li>
* <li>范围校验分页大小必须在 5 100 之间</li>
* <li>自定义校验分页大小必须为 5 的整数倍</li>
* </ul>
* </p>
* <p>
* 示例
* <pre>
* pagedReq.setPageSize(20L);
* </pre>
* </p>
*/
@Schema(description = "分页大小最小值为5 最大值100 取值必须为5的整数倍")
@NotNull(message = "pageSize {item.not_null}", groups = ValidGroups.BasePagedReqValid.class)
@ValidPageSize(message = "pageSize {page.item_size}", groups = ValidGroups.BasePagedReqValid.class)
@Range(min = 5, max = 100, message = "pageSize {item.value_range}", groups = ValidGroups.BasePagedReqValid.class)
private Long pageSize;
/**
* 数据总数
* <p>
* 可选字段用于指定分页对应的数据总条数如果未知可以设置为空或小于 0
* 该字段在某些场景下可帮助后端优化查询性能
* </p>
* <p>
* 示例
* <pre>
* pagedReq.setTotalSize(100L);
* </pre>
* </p>
*/
@Schema(description = "数据总数未知时可以为空或者小于0 存在时可以加快后端数据库查询速度")
private Long totalSize;
}

View File

@ -0,0 +1,76 @@
package com.cmhi.magent.pojo.dto;
import com.cmhi.magent.pojo.base.BaseProtocol;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 协议请求类 {@code ProtocolReq}
* <p>
* 该类是一个泛型类用于封装具体的协议请求数据它继承自 {@link BaseProtocol}
* 保留了协议的通用字段如版本号加密类型时间戳等同时可以通过泛型 {@code T}
* 指定具体的请求内容结构
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>继承自 {@link BaseProtocol}提供协议基础结构的支持</li>
* <li>通过泛型 {@code T} 支持动态定义协议请求的内容</li>
* <li>便于与 `BaseProtocol` 配套使用用于封装请求数据</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于封装网络通信中具体的请求协议数据</li>
* <li>用于需要统一协议格式的场景尤其是与 {@link BaseProtocol} 配套使用</li>
* </ul>
* </p>
*
* <p>
* 继承关系
* <ul>
* <li>继承自 {@link BaseProtocol}复用其通用字段和功能</li>
* <li>支持完全相同的字段校验规则</li>
* </ul>
* </p>
*
* <p>
* 使用示例
* <pre>
* ProtocolReq<MyRequestData> request = new ProtocolReq<>();
* request.setVer(1);
* request.setCryptoType(0);
* request.setTimeStamp(System.currentTimeMillis());
* request.setMsgContent(new MyRequestData());
* </pre>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "ver": 1,
* "cryptoType": 0,
* "timeStamp": 1672531199000,
* "msgContent": {
* // 请求内容根据 T 类型决定具体结构
* }
* }
* </pre>
* </p>
*
* @param <T> 请求内容的类型由具体业务决定
* @see BaseProtocol
* @see lombok.NoArgsConstructor
* @see lombok.ToString
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@NoArgsConstructor
@ToString
public class ProtocolReq<T> extends BaseProtocol<T> {
}

View File

@ -0,0 +1,157 @@
package com.cmhi.magent.pojo.mapper;
import com.cmhi.magent.pojo.po.HwFirmware;
import com.cmhi.magent.pojo.po.HwInfo;
import com.cmhi.magent.pojo.po.HwMotherBoard;
import com.cmhi.magent.pojo.po.ProcessorInfo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import oshi.hardware.Baseboard;
import oshi.hardware.CentralProcessor;
import oshi.hardware.ComputerSystem;
import oshi.hardware.Firmware;
/**
* 接口 {@code IObjectConvert} 用于对象之间的转换操作基于 MapStruct 框架实现
* <p>
* 该接口的作用是将硬件相关信息从 OSHI 库定义的对象转换为自定义的 PO 对象
* 包括硬件固件信息主板信息计算机系统信息以及处理器信息等
* 使用 MapStruct 提供的注解和方法自动生成代码以实现高效的对象映射
* </p>
*
* <p>
* 核心功能
* <ul>
* <li> OSHI 硬件对象 {@link Firmware}, {@link Baseboard}映射为自定义 PO 对象</li>
* <li>支持嵌套对象的转换如将 {@link ComputerSystem} 的固件和主板信息转换为对应的自定义对象</li>
* <li>通过 MapStruct 的表达式功能实现自定义字段映射逻辑</li>
* </ul>
* </p>
*
* @see Mapper
* @see org.mapstruct.factory.Mappers
* @see HwFirmware
* @see HwMotherBoard
* @see HwInfo
* @see ProcessorInfo
* @see Firmware
* @see Baseboard
* @see ComputerSystem
* @see CentralProcessor
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Mapper
@SuppressWarnings("java:S1710")
public interface IObjectConvert {
/**
* 单例实例通过 MapStruct 自动生成的实现类进行实例化
*/
IObjectConvert INSTANCE = Mappers.getMapper(IObjectConvert.class);
/**
* OSHI {@link Firmware} 对象映射为自定义的 {@link HwFirmware} 对象
* <p>
* 字段映射规则
* <ul>
* <li>{@code Firmware.getName()} -> {@code HwFirmware.name}</li>
* <li>{@code Firmware.getVersion()} -> {@code HwFirmware.version}</li>
* <li>{@code Firmware.getDescription()} -> {@code HwFirmware.description}</li>
* <li>{@code Firmware.getManufacturer()} -> {@code HwFirmware.manufacturer}</li>
* <li>{@code Firmware.getReleaseDate()} -> {@code HwFirmware.releaseDate}</li>
* </ul>
* </p>
*
* @param f {@link Firmware} 对象表示硬件固件信息
* @return {@link HwFirmware} 对象表示自定义的硬件固件信息
*/
@Mappings({@Mapping(expression = "java(f.getName())", target = "name"),
@Mapping(expression = "java(f.getVersion())", target = "version"),
@Mapping(expression = "java(f.getDescription())", target = "description"),
@Mapping(expression = "java(f.getManufacturer())", target = "manufacturer"),
@Mapping(expression = "java(f.getReleaseDate())", target = "releaseDate")})
HwFirmware toHwFirmware(Firmware f);
/**
* OSHI {@link Baseboard} 对象映射为自定义的 {@link HwMotherBoard} 对象
* <p>
* 字段映射规则
* <ul>
* <li>{@code Baseboard.getVersion()} -> {@code HwMotherBoard.version}去除前后空格</li>
* <li>{@code Baseboard.getManufacturer()} -> {@code HwMotherBoard.manufacturer}</li>
* <li>{@code Baseboard.getModel()} -> {@code HwMotherBoard.model}</li>
* <li>{@code Baseboard.getSerialNumber()} -> {@code HwMotherBoard.serialNumber}</li>
* </ul>
* </p>
*
* @param b {@link Baseboard} 对象表示主板信息
* @return {@link HwMotherBoard} 对象表示自定义的主板信息
*/
@Mappings({@Mapping(expression = "java(b.getVersion().trim())", target = "version"),
@Mapping(expression = "java(b.getManufacturer())", target = "manufacturer"),
@Mapping(expression = "java(b.getModel())", target = "model"),
@Mapping(expression = "java(b.getSerialNumber())", target = "serialNumber")})
HwMotherBoard toHwMotherBoard(Baseboard b);
/**
* OSHI {@link ComputerSystem} 对象映射为自定义的 {@link HwInfo} 对象
* <p>
* 字段映射规则
* <ul>
* <li>{@code ComputerSystem.getModel()} -> {@code HwInfo.model}</li>
* <li>{@code ComputerSystem.getManufacturer()} -> {@code HwInfo.manufacturer}</li>
* <li>{@code ComputerSystem.getHardwareUUID()} -> {@code HwInfo.uuid}</li>
* <li>{@code ComputerSystem.getSerialNumber()} -> {@code HwInfo.serialNumber}</li>
* <li>{@code ComputerSystem.getFirmware()} -> {@code HwInfo.firmware}</li>
* <li>{@code ComputerSystem.getBaseboard()} -> {@code HwInfo.mb}</li>
* </ul>
* </p>
*
* @param c {@link ComputerSystem} 对象表示计算机系统信息
* @return {@link HwInfo} 对象表示自定义的计算机系统信息
*/
@Mappings({@Mapping(expression = "java(c.getModel())", target = "model"),
@Mapping(expression = "java(c.getManufacturer())", target = "manufacturer"),
@Mapping(expression = "java(c.getHardwareUUID())", target = "uuid"),
@Mapping(expression = "java(c.getSerialNumber())", target = "serialNumber"),
@Mapping(expression = "java(toHwFirmware(c.getFirmware()))", target = "firmware"),
@Mapping(expression = "java(toHwMotherBoard(c.getBaseboard()))", target = "mb")})
HwInfo toHwInfo(ComputerSystem c);
/**
* OSHI {@link CentralProcessor} 对象映射为自定义的 {@link ProcessorInfo} 对象
* <p>
* 字段映射规则
* <ul>
* <li>{@code CentralProcessor.getProcessorIdentifier().getVendor()} -> {@code ProcessorInfo.cpuVendor}</li>
* <li>{@code CentralProcessor.getProcessorIdentifier().getName()} -> {@code ProcessorInfo.cpuName}</li>
* <li>{@code CentralProcessor.getProcessorIdentifier().getProcessorID()} -> {@code ProcessorInfo.processorId}</li>
* <li>{@code CentralProcessor.getProcessorIdentifier().getIdentifier()} -> {@code ProcessorInfo.cpuIdentifier}</li>
* <li>{@code CentralProcessor.getProcessorIdentifier().getMicroarchitecture()} -> {@code ProcessorInfo.microArchitecture}</li>
* <li>{@code CentralProcessor.getProcessorIdentifier().isCpu64bit()} -> {@code ProcessorInfo.cpu64bit}</li>
* <li>{@code CentralProcessor.getMaxFreq()} -> {@code ProcessorInfo.cpuVendorFreq}</li>
* <li>{@code CentralProcessor.getPhysicalPackageCount()} -> {@code ProcessorInfo.physicalCpus}</li>
* <li>{@code CentralProcessor.getPhysicalProcessorCount()} -> {@code ProcessorInfo.physicalCores}</li>
* <li>{@code CentralProcessor.getLogicalProcessorCount()} -> {@code ProcessorInfo.logicalCpu}</li>
* </ul>
* </p>
*
* @param p {@link CentralProcessor} 对象表示处理器信息
* @param i {@link CentralProcessor.ProcessorIdentifier} 对象表示详细的处理器标识信息
* @return {@link ProcessorInfo} 对象表示自定义的处理器信息
*/
@Mappings({@Mapping(expression = "java(p.getProcessorIdentifier().getVendor())", target = "cpuVendor"),
@Mapping(expression = "java(p.getProcessorIdentifier().getName())", target = "cpuName"),
@Mapping(expression = "java(p.getProcessorIdentifier().getProcessorID())", target = "processorId"),
@Mapping(expression = "java(p.getProcessorIdentifier().getIdentifier())", target = "cpuIdentifier"),
@Mapping(expression = "java(p.getProcessorIdentifier().getMicroarchitecture())", target = "microArchitecture"),
@Mapping(expression = "java(p.getProcessorIdentifier().isCpu64bit())", target = "cpu64bit"),
@Mapping(expression = "java(p.getMaxFreq())", target = "cpuVendorFreq"),
@Mapping(expression = "java(p.getPhysicalPackageCount())", target = "physicalCpus"),
@Mapping(expression = "java(p.getPhysicalProcessorCount())", target = "physicalCores"),
@Mapping(expression = "java(p.getLogicalProcessorCount())", target = "logicalCpu")})
ProcessorInfo toProcessorInfo(CentralProcessor p, CentralProcessor.ProcessorIdentifier i);
}

View File

@ -0,0 +1,112 @@
package com.cmhi.magent.pojo.po;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* API 上下文信息封装类 {@code ApiContext}
* <p>
* 该类用于封装与 API 调用相关的上下文信息常用于追踪请求认证和用户信息
* 通过封装请求的本地信息请求体时间戳用户信息等字段便于后续的日志记录调试和分析
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装 API 请求的上下文信息如请求的本地信息请求体时间戳等</li>
* <li>支持用户认证相关字段 JWT用户名和用户 ID</li>
* <li>易于扩展便于在多种场景下使用</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于记录 API 请求的上下文信息支持日志记录和调试</li>
* <li>支持后端服务间传递上下文信息便于追踪和分析请求流向</li>
* </ul>
* </p>
*
* <p>
* 示例
* <pre>
* ApiContext context = ApiContext.builder()
* .reqLocal("127.0.0.1")
* .requestBody("{\"key\":\"value\"}")
* .requestTime(System.currentTimeMillis())
* .jwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
* .username("user123")
* .uid("1001")
* .id(1L)
* .build();
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiContext {
/**
* 请求来源的本地信息
* <p>
* 表示请求来源的 IP 地址或本地标识例如"127.0.0.1" 表示本机
* </p>
*/
private String reqLocal;
/**
* 请求体内容
* <p>
* 表示当前请求的具体内容可以是 JSON 字符串或其他格式的请求数据
* </p>
*/
private String requestBody;
/**
* 请求时间戳
* <p>
* 表示请求的发起时间采用毫秒级时间戳例如通过 {@code System.currentTimeMillis()} 获取
* </p>
*/
private Long requestTime;
/**
* 用户的 JWTJSON Web Token
* <p>
* 表示用户的认证信息通过 JWT 进行身份验证和授权
* </p>
*/
private String jwt;
/**
* 用户名
* <p>
* 表示当前请求用户的用户名用于标识用户的身份
* </p>
*/
private String username;
/**
* 用户唯一标识符
* <p>
* 表示用户的唯一 ID用于区分不同的用户
* </p>
*/
private String uid;
/**
* 请求的唯一标识符
* <p>
* 表示该请求的唯一 ID可用于请求追踪或日志记录
* </p>
*/
private Long id;
}

View File

@ -0,0 +1,100 @@
package com.cmhi.magent.pojo.po;
import com.cmhi.magent.common.ErrorCode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 通用响应状态封装类 {@code BaseRespStatus}
* <p>
* 该类用于封装接口响应的基础状态信息包括状态码 {@code status} 和消息 {@code message}
* 默认状态为成功状态码为 {@code ErrorCode.ERR_OK.getCode()}消息为 {@code ErrorCode.ERR_OK.getDescription()}
* 具体接口可以通过继承该类扩展其他字段以适配业务需要
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>提供统一的状态码字段 {@code status}便于接口响应的状态管理</li>
* <li>支持多条消息描述字段 {@code message}便于传递更详细的状态信息</li>
* <li>默认状态为成功便于简化正常响应的构建</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>适用于所有需要返回状态码和状态描述的接口</li>
* <li>作为基础类支持扩展以适配更多字段需求</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "status": 0,
* "message": ["操作成功"]
* }
* </pre>
* </p>
*
* <p>
* 默认值说明
* <ul>
* <li>{@code status} 默认值为 {@code ErrorCode.ERR_OK.getCode()}表示操作成功</li>
* <li>{@code message} 默认值为 {@code ErrorCode.ERR_OK.getDescription()} "操作成功"</li>
* </ul>
* </p>
*
* @see ErrorCode
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseRespStatus {
/**
* 响应状态码
* <p>
* 表示接口调用的执行状态默认值为 {@code ErrorCode.ERR_OK.getCode()}即操作成功
* 可以通过 {@link ErrorCode} 枚举类获取所有状态码
* </p>
*
* <p>
* 示例
* <ul>
* <li>0操作成功</li>
* <li>1操作失败</li>
* <li>400请求参数错误</li>
* </ul>
* </p>
*
* @see ErrorCode
*/
@Schema(description = "响应状态码。0表示成功其他值表示失败可参考ErrorCode类。", example = "0")
private Integer status = ErrorCode.ERR_OK.getCode();
/**
* 响应消息描述
* <p>
* 表示接口调用的状态描述信息支持多条消息内容
* 默认值为 {@code ErrorCode.ERR_OK.getDescription()} "操作成功"
* </p>
*
* <p>
* 示例
* <ul>
* <li>["操作成功"]</li>
* <li>["参数错误", "字段name不能为空"]</li>
* </ul>
* </p>
*/
@Schema(description = "响应消息描述,支持多条消息内容。", example = "[\"操作成功\"]")
private String[] message = new String[] {ErrorCode.ERR_OK.getDescription()};
}

View File

@ -0,0 +1,95 @@
package com.cmhi.magent.pojo.po;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 硬件固件信息类 {@code HwFirmware}
* <p>
* 用于描述硬件固件的基本信息包括名称版本描述制造商以及发布日期等字段
* 该类可用于描述设备固件属性便于在系统中进行固件信息的管理和展示
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装固件的基本信息字段如名称版本描述等</li>
* <li>通过 {@link JsonPropertyOrder} 注解定义 JSON 输出字段顺序</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于描述设备或硬件的固件详细信息</li>
* <li>可应用于系统固件管理模块</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "name": "固件A",
* "version": "1.0.0",
* "manufacturer": "公司XYZ",
* "releaseDate": "2025-01-01",
* "description": "这是固件的描述信息"
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@JsonPropertyOrder({"name", "version", "manufacturer", "releaseDate", "description"})
public class HwFirmware {
/**
* 固件名称
* <p>
* 表示固件的完整名称例如 "CPU固件"
* </p>
*/
@Schema(description = "固件名称,用于标识具体固件。", example = "CPU固件")
private String name;
/**
* 固件版本
* <p>
* 表示固件的版本号例如 "1.0.0"
* </p>
*/
@Schema(description = "固件的版本号。", example = "1.0.0")
private String version;
/**
* 固件描述
* <p>
* 描述固件的详细信息例如用途或特性
* </p>
*/
@Schema(description = "固件的详细描述信息。", example = "这是固件的描述信息")
private String description;
/**
* 制造商名称
* <p>
* 表示固件的制造商或开发公司例如 "公司XYZ"
* </p>
*/
@Schema(description = "固件制造商的名称。", example = "公司XYZ")
private String manufacturer;
/**
* 固件发布日期
* <p>
* 表示固件的发布时间使用 ISO 8601 格式的日期字符串例如 "2025-01-01"
* </p>
*/
@Schema(description = "固件的发布日期格式为YYYY-MM-DD。", example = "2025-01-01")
private String releaseDate;
}

View File

@ -0,0 +1,117 @@
package com.cmhi.magent.pojo.po;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 硬件信息类 {@code HwInfo}
* <p>
* 用于描述设备硬件的基本信息包括型号制造商唯一标识符序列号固件信息以及主板信息等
* 该类可以作为设备硬件管理模块的核心数据模型用于记录和展示设备的详细硬件属性
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装设备的基本硬件信息如型号制造商等</li>
* <li>支持嵌套其他硬件相关类如固件信息和主板信息</li>
* <li>通过 {@link JsonPropertyOrder} 注解定义 JSON 输出字段顺序</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于描述设备的硬件信息</li>
* <li>可应用于硬件管理或设备配置展示模块</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "model": "设备型号A",
* "manufacturer": "公司XYZ",
* "uuid": "123e4567-e89b-12d3-a456-426655440000",
* "serialNumber": "SN123456789",
* "firmware": {
* "name": "固件A",
* "version": "1.0.0",
* "manufacturer": "公司XYZ",
* "releaseDate": "2025-01-01",
* "description": "这是固件的描述信息"
* },
* "mb": {
* "name": "主板型号B",
* "chipset": "芯片组XYZ",
* "manufacturer": "公司XYZ",
* "serialNumber": "MB123456789"
* }
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@JsonPropertyOrder({"model", "manufacturer", "uuid", "serialNumber", "firmware", "mb"})
public class HwInfo {
/**
* 硬件型号
* <p>
* 表示设备的型号名称例如 "设备型号A"
* </p>
*/
@Schema(description = "硬件型号名称。", example = "设备型号A")
private String model;
/**
* 硬件制造商
* <p>
* 表示设备的制造商名称例如 "公司XYZ"
* </p>
*/
@Schema(description = "硬件制造商的名称。", example = "公司XYZ")
private String manufacturer;
/**
* 硬件唯一标识符
* <p>
* 表示硬件的 UUID例如 "123e4567-e89b-12d3-a456-426655440000"
* </p>
*/
@Schema(description = "硬件的唯一标识符 (UUID)。", example = "123e4567-e89b-12d3-a456-426655440000")
private String uuid;
/**
* 硬件序列号
* <p>
* 表示设备的序列号例如 "SN123456789"
* </p>
*/
@Schema(description = "硬件的序列号。", example = "SN123456789")
private String serialNumber;
/**
* 固件信息
* <p>
* 使用 {@link HwFirmware} 类型表示硬件的固件详细信息包括名称版本制造商等
* </p>
*/
@Schema(description = "硬件的固件信息,包括固件名称、版本、制造商等。")
private HwFirmware firmware;
/**
* 主板信息
* <p>
* 使用 {@link HwMotherBoard} 类型表示硬件主板的详细信息包括名称芯片组制造商等
* </p>
*/
@Schema(description = "硬件的主板信息,包括主板型号、芯片组、制造商等。")
private HwMotherBoard mb;
}

View File

@ -0,0 +1,85 @@
package com.cmhi.magent.pojo.po;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 主板信息类 {@code HwMotherBoard}
* <p>
* 用于描述硬件主板的基本信息包括版本号制造商型号以及序列号等字段
* 该类可作为设备硬件管理模块中的主板属性描述便于记录和展示主板的详细信息
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装主板的基本属性字段如版本号制造商等</li>
* <li>通过 {@link JsonPropertyOrder} 注解定义 JSON 输出字段顺序</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于描述设备主板的基本信息例如系统硬件配置模块</li>
* <li>可用于设备主板信息的查询和展示</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "version": "1.0.0",
* "manufacturer": "公司XYZ",
* "model": "主板型号A",
* "serialNumber": "MB123456789"
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@JsonPropertyOrder({"version", "manufacturer", "model", "serialNumber"})
public class HwMotherBoard {
/**
* 主板版本号
* <p>
* 表示主板的版本例如 "1.0.0"
* </p>
*/
@Schema(description = "主板的版本号。", example = "1.0.0")
private String version;
/**
* 主板制造商
* <p>
* 表示主板的制造商名称例如 "公司XYZ"
* </p>
*/
@Schema(description = "主板的制造商名称。", example = "公司XYZ")
private String manufacturer;
/**
* 主板型号名称
* <p>
* 表示主板的型号例如 "主板型号A"
* </p>
*/
@Schema(description = "主板的型号名称。", example = "主板型号A")
private String model;
/**
* 主板序列号
* <p>
* 表示主板的唯一序列号例如 "MB123456789"
* </p>
*/
@Schema(description = "主板的序列号。", example = "MB123456789")
private String serialNumber;
}

View File

@ -0,0 +1,188 @@
package com.cmhi.magent.pojo.po;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
/**
* 操作日志类 {@code OperationLog}
* <p>
* 用于记录系统中用户或服务的操作行为包含模块名称操作类型操作状态请求信息
* 执行耗时等详细字段便于系统审计和问题排查
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>记录操作的基本信息如模块名称操作类型等</li>
* <li>包含请求和响应的详细信息 HTTP 方法路径头信息等</li>
* <li>记录操作耗时和延迟便于分析性能</li>
* <li>支持通过构造器或建造者模式创建对象</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于记录用户操作日志例如管理系统中的关键行为</li>
* <li>应用于接口访问日志记录模块用于审计和回溯</li>
* <li>便于监控和优化系统性能</li>
* </ul>
* </p>
*
* <p>
* 示例
* <pre>
* OperationLog log = OperationLog.builder()
* .module("用户管理")
* .operationType("查询")
* .operationStatus("成功")
* .description("查询用户列表")
* .requestIp("192.168.0.1")
* .httpMethod("GET")
* .httpPath("/api/users")
* .expendTime(120L)
* .operationTime(new Timestamp(System.currentTimeMillis()))
* .build();
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OperationLog {
/**
* 模块名称
* <p>
* 表示操作所属的业务模块例如 "用户管理"
* </p>
*/
@Schema(description = "操作所属的业务模块。", example = "用户管理")
private String module;
/**
* 操作类型
* <p>
* 表示操作的类别例如 "查询""新增""删除"
* </p>
*/
@Schema(description = "操作的类型或类别。", example = "查询")
private String operationType;
/**
* 操作状态
* <p>
* 表示操作的结果状态例如 "成功" "失败"
* </p>
*/
@Schema(description = "操作的执行状态。", example = "成功")
private String operationStatus;
/**
* 操作描述
* <p>
* 对操作的简要说明例如 "查询用户列表"
* </p>
*/
@Schema(description = "操作的简要描述。", example = "查询用户列表")
private String description;
/**
* 请求来源 IP
* <p>
* 记录操作的来源 IP例如 "192.168.0.1"
* </p>
*/
@Schema(description = "请求的来源 IP 地址。", example = "192.168.0.1")
private String requestIp;
/**
* 调用的函数或方法名称
* <p>
* 表示执行操作时调用的方法名称</p>
*/
@Schema(description = "执行操作时调用的函数或方法名称。", example = "getUserList")
private String callFunction;
/**
* HTTP 请求方法
* <p>
* 记录操作对应的 HTTP 请求方法例如 "GET""POST"
* </p>
*/
@Schema(description = "HTTP 请求的请求方法。", example = "GET")
private String httpMethod;
/**
* HTTP 请求路径
* <p>
* 表示操作访问的具体接口路径例如 "/api/users"
* </p>
*/
@Schema(description = "HTTP 请求的接口路径。", example = "/api/users")
private String httpPath;
/**
* 操作耗时毫秒
* <p>
* 表示操作执行的时间消耗例如 120 毫秒
* </p>
*/
@Schema(description = "操作执行的耗时(单位:毫秒)。", example = "120")
private Long expendTime;
/**
* 数据传输延迟毫秒
* <p>
* 表示操作中数据传输的延迟时间</p>
*/
@Schema(description = "数据传输的延迟时间(单位:毫秒)。", example = "15")
private Long transmitDelay;
/**
* 请求头
* <p>
* 记录 HTTP 请求的头部信息通常以 JSON 字符串形式存储
* </p>
*/
@Schema(description = "HTTP 请求头信息(以 JSON 格式存储)。",
example = "{ 'Content-Type': 'application/json' }")
private String requestHeaders;
/**
* 请求内容
* <p>
* 记录 HTTP 请求的具体内容例如请求体
* </p>
*/
@Schema(description = "HTTP 请求的具体内容。", example = "{ 'username': 'admin' }")
private String request;
/**
* 响应结果
* <p>
* 记录操作的响应结果例如返回值或错误信息
* </p>
*/
@Schema(description = "操作的响应结果。", example = "{ 'status': 200, 'message': 'Success' }")
private String result;
/**
* 操作时间
* <p>
* 表示记录操作的具体时间戳
* </p>
*/
@Schema(description = "操作发生的时间。", example = "2023-01-07T12:00:00.000+00:00")
private Timestamp operationTime;
}

View File

@ -0,0 +1,110 @@
package com.cmhi.magent.pojo.po;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 分页结果类 {@code PageResults}
* <p>
* 用于封装分页查询结果包括当前页码页大小总页数总行数以及数据项列表
* 支持通过泛型参数 {@code T} 指定数据项的类型适用于常见的分页接口场景
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>支持分页结果的数据封装如页码总行数等</li>
* <li>通过泛型参数 {@code T} 表示数据项的类型便于复用</li>
* <li>通过 {@link JsonPropertyOrder} 注解定义 JSON 输出字段顺序</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于 RESTful API 的分页响应结果封装</li>
* <li>适用于分页查询接口例如列表数据查询等</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "pageNumber": 1,
* "pageSize": 10,
* "totalPage": 5,
* "totalRow": 50,
* "items": [
* { "id": 1, "name": "Item 1" },
* { "id": 2, "name": "Item 2" }
* ]
* }
* </pre>
* </p>
*
* @param <T> 数据项的类型
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"pageNumber", "pageSize", "totalPage", "totalRow", "items"})
public class PageResults<T> {
/**
* 数据项列表
* <p>
* 表示当前页返回的具体数据项类型由泛型参数 {@code T} 决定
* </p>
*/
@Schema(description = "当前页的具体数据项列表。",
example = "[{ 'id': 1, 'name': 'Item 1' }, { 'id': 2, 'name': 'Item 2' }]")
private List<T> items;
/**
* 当前页码
* <p>
* 表示当前分页的页码 1 开始计数
* </p>
*/
@Schema(description = "当前分页的页码,从 1 开始。", example = "1")
private long pageNumber;
/**
* 每页大小
* <p>
* 表示每页包含的数据项数量
* </p>
*/
@Schema(description = "每页包含的数据项数量。", example = "10")
private long pageSize;
/**
* 总页数
* <p>
* 表示分页结果的总页数
* </p>
*/
@Schema(description = "分页结果的总页数。", example = "5")
private long totalPage;
/**
* 总行数
* <p>
* 表示所有数据项的总行数
* </p>
*/
@Schema(description = "所有数据项的总行数。", example = "50")
private long totalRow;
}

View File

@ -0,0 +1,147 @@
package com.cmhi.magent.pojo.po;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 处理器信息类 {@code ProcessorInfo}
* <p>
* 用于描述系统处理器CPU的详细信息包括处理器的厂商型号架构等关键属性
* 该类可用于系统硬件管理模块便于采集和展示处理器详细信息
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装处理器的基本信息如厂商名称架构等</li>
* <li>支持通过 {@link JsonPropertyOrder} 注解定义 JSON 输出顺序</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于描述设备的处理器详细信息例如服务器或个人计算机的处理器配置</li>
* <li>适用于硬件设备监控或系统配置展示模块</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "cpuVendor": "Intel",
* "cpuName": "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz",
* "processorId": "BFEBFBFF000306C3",
* "cpuIdentifier": "x86_64 Family 6 Model 158 Stepping 10",
* "microArchitecture": "Comet Lake",
* "cpu64bit": true,
* "cpuVendorFreq": 3800000000,
* "physicalCpus": 1,
* "physicalCores": 8,
* "logicalCpu": 16
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@JsonPropertyOrder({"cpuVendor", "cpuName", "processorId", "cpuIdentifier", "microArchitecture", "cpu64bit", "cpuVendorFreq",
"physicalCpus", "physicalCores", "logicalCpu"})
public class ProcessorInfo {
/**
* CPU 厂商
* <p>
* 表示处理器的制造商名称例如 "Intel""AMD"
* </p>
*/
@Schema(description = "处理器的制造商名称。", example = "Intel")
private String cpuVendor;
/**
* CPU 名称
* <p>
* 表示处理器的完整型号名称例如 "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz"
* </p>
*/
@Schema(description = "处理器的完整型号名称。",
example = "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz")
private String cpuName;
/**
* CPU 标识符Processor ID
* <p>
* 表示处理器的唯一标识符例如 "BFEBFBFF000306C3"
* </p>
*/
@Schema(description = "处理器的唯一标识符。", example = "BFEBFBFF000306C3")
private String processorId;
/**
* CPU 标识信息
* <p>
* 提供额外的处理器标识信息例如 "x86_64 Family 6 Model 158 Stepping 10"
* </p>
*/
@Schema(description = "处理器的标识信息。", example = "x86_64 Family 6 Model 158 Stepping 10")
private String cpuIdentifier;
/**
* 微架构Micro-Architecture
* <p>
* 表示处理器的微架构名称例如 "Comet Lake"
* </p>
*/
@Schema(description = "处理器的微架构名称。", example = "Comet Lake")
private String microArchitecture;
/**
* 是否为 64 位架构
* <p>
* 表示处理器是否支持 64 位架构
* </p>
*/
@Schema(description = "处理器是否支持 64 位架构。", example = "true")
private Boolean cpu64bit;
/**
* CPU 主频Hz
* <p>
* 表示处理器的厂商标称主频单位Hz例如 3800000000 表示 3.80GHz
* </p>
*/
@Schema(description = "处理器的标称主频单位Hz", example = "3800000000")
private Long cpuVendorFreq;
/**
* 物理 CPU 数量
* <p>
* 表示系统中物理处理器的数量
* </p>
*/
@Schema(description = "系统中物理处理器的数量。", example = "1")
private Integer physicalCpus;
/**
* 物理核心数量
* <p>
* 表示处理器的物理核心数例如 8
* </p>
*/
@Schema(description = "处理器的物理核心数。", example = "8")
private Integer physicalCores;
/**
* 逻辑核心数量
* <p>
* 表示处理器的逻辑核心数包括超线程例如 16
* </p>
*/
@Schema(description = "处理器的逻辑核心数(包括超线程)。", example = "16")
private Integer logicalCpu;
}

View File

@ -0,0 +1,109 @@
package com.cmhi.magent.pojo.po;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 版本信息类 {@code VersionInfo}
* <p>
* 用于描述系统版本的关键信息包括代码提交记录构建时间分支信息等
* 该类可用于版本管理模块便于展示或记录系统的版本状态
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装系统版本的关键信息如提交 ID提交描述构建时间等</li>
* <li>支持通过建造者模式{@link Builder}快速构建对象</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于系统版本信息展示模块例如前端关于页面或者系统管理模块</li>
* <li>适用于调试或诊断系统版本状态便于问题排查</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "commitId": "abc12345",
* "commitDescribe": "Fix bug in user login",
* "commitTime": "2023-01-07T12:00:00Z",
* "tagName": "v1.0.0",
* "buildTime": "2023-01-07T14:00:00Z",
* "gitBranch": "main"
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class VersionInfo {
/**
* 提交 ID
* <p>
* 表示系统当前版本的代码提交记录的唯一标识例如 "abc12345"
* </p>
*/
@Schema(description = "系统当前版本的代码提交记录的唯一标识。", example = "abc12345")
private String commitId;
/**
* 提交描述
* <p>
* 表示系统当前版本的代码提交的具体描述信息例如 "Fix bug in user login"
* </p>
*/
@Schema(description = "系统当前版本的代码提交的具体描述信息。", example = "Fix bug in user login")
private String commitDescribe;
/**
* 提交时间
* <p>
* 表示代码提交的时间戳例如 "2023-01-07T12:00:00Z"
* </p>
*/
@Schema(description = "代码提交的时间戳。", example = "2023-01-07T12:00:00Z")
private String commitTime;
/**
* 标签名称
* <p>
* 表示系统当前版本的标签信息例如 "v1.0.0"
* </p>
*/
@Schema(description = "系统当前版本的标签信息。", example = "v1.0.0")
private String tagName;
/**
* 构建时间
* <p>
* 表示当前系统版本构建的时间戳例如 "2023-01-07T14:00:00Z"
* </p>
*/
@Schema(description = "系统版本的构建时间戳。", example = "2023-01-07T14:00:00Z")
private String buildTime;
/**
* Git 分支
* <p>
* 表示代码版本对应的 Git 分支名称例如 "main"
* </p>
*/
@Schema(description = "代码版本对应的 Git 分支名称。", example = "main")
private String gitBranch;
}

View File

@ -0,0 +1,85 @@
package com.cmhi.magent.pojo.vo;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.po.PageResults;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 基础分页结果响应类 {@code BasePageResultResp}
* <p>
* 用于封装包含分页数据的统一响应结果继承自 {@link BaseRespStatus}包含状态码和消息
* 同时将分页数据封装在 {@link PageResults} 对象中
* 该类支持通过泛型 {@code T} 指定分页数据项的类型
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>继承基础响应状态类统一包含状态码和消息</li>
* <li>支持分页数据的封装包含分页元信息和数据项列表</li>
* <li>通过 {@link JsonInclude} 注解对空字段进行排除提高 JSON 响应的简洁性</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于 RESTful API 的分页响应结果封装适合分页查询接口 </li>
* <li>适用于需要返回分页数据列表的场景例如表格数据展示</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "status": "SUCCESS",
* "message": "Query executed successfully",
* "items": {
* "pageNumber": 1,
* "pageSize": 10,
* "totalPage": 5,
* "totalRow": 50,
* "items": [
* { "id": 1, "name": "Item 1" },
* { "id": 2, "name": "Item 2" }
* ]
* }
* }
* </pre>
* </p>
*
* @param <T> 分页数据项的类型
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@EqualsAndHashCode(callSuper = true)
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"items", "status", "message"})
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BasePageResultResp<T> extends BaseRespStatus {
/**
* 分页数据
* <p>
* 包含分页元信息如当前页码总页数总行数等以及具体的数据项列表
* 类型由泛型 {@code T} 指定
* </p>
*/
@Schema(description = "分页数据,包含分页元信息(如当前页码、总页数、总行数等)以及具体数据项。",
example = "{ \"pageNumber\": 1, \"pageSize\": 10, \"totalPage\": 5, \"totalRow\": 50, " +
"\"items\": [ {\"id\": 1, \"name\": " + "\"Item 1\"}, {\"id\": 2, \"name\": " +
"\"Item 2\"} ] }")
private PageResults<T> items;
}

View File

@ -0,0 +1,73 @@
package com.cmhi.magent.pojo.vo;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.po.HwInfo;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 系统硬件信息响应类 {@code GetHwInfoResp}
* <p>
* 用于封装系统硬件信息的响应结果继承自 {@link BaseRespStatus}包含状态码消息以及硬件信息内容
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>继承基础响应状态类统一包含状态码和消息</li>
* <li>封装硬件信息内容通过 {@code hwInfo} 字段提供详细的硬件描述</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于 RESTful API 的硬件信息查询结果响应</li>
* <li>适用于系统监控模块提供硬件配置信息</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "status": "SUCCESS",
* "message": "System hardware information retrieved successfully",
* "hwInfo": {
* "cpu": "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz",
* "ram": "16GB",
* "storage": "512GB SSD",
* "os": "Ubuntu 20.04"
* }
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Schema(name = "系统硬件信息")
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@JsonPropertyOrder({"hwInfo", "status", "message"})
@AllArgsConstructor
@NoArgsConstructor
public class GetHwInfoResp extends BaseRespStatus {
/**
* 硬件信息内容
* <p>
* 包含系统的硬件配置信息例如 CPU 型号内存大小存储容量和操作系统版本等
* 类型为 {@link HwInfo}
* </p>
*/
@Schema(description = "硬件信息内容,例如 CPU 型号、内存大小、存储容量和操作系统版本")
private HwInfo hwInfo;
}

View File

@ -0,0 +1,80 @@
package com.cmhi.magent.pojo.vo;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
/**
* 操作系统信息响应类 {@code GetOsInfoResp}
* <p>
* 用于封装操作系统信息的响应结果继承自 {@link BaseRespStatus}包含状态码消息
* 操作系统名称以及系统的启动时间
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>继承基础响应状态类统一包含状态码和消息</li>
* <li>封装操作系统的相关信息包括操作系统名称和启动时间</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>适用于 RESTful API 的操作系统信息查询接口</li>
* <li>用于系统监控模块提供操作系统的基础信息</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "status": "SUCCESS",
* "message": "Operating system information retrieved successfully",
* "os": "Ubuntu 20.04",
* "bootTime": "2023-01-07T08:00:00"
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Schema(name = "操作系统信息")
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@JsonPropertyOrder({"os", "bootTime", "status", "message"})
@AllArgsConstructor
@NoArgsConstructor
public class GetOsInfoResp extends BaseRespStatus {
/**
* 操作系统名称
* <p>
* 表示系统的操作系统名称例如 "Ubuntu 20.04" "Windows 10"
* </p>
*/
@Schema(description = "操作系统名称,例如 Ubuntu 20.04 或 Windows 10")
private String os;
/**
* 系统启动时间
* <p>
* 表示系统最近一次启动的时间格式为 {@link Timestamp}
* 例如 "2023-01-07T08:00:00"
* </p>
*/
@Schema(description = "系统启动时间,例如 2023-01-07T08:00:00")
private Timestamp bootTime;
}

View File

@ -0,0 +1,73 @@
package com.cmhi.magent.pojo.vo;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.po.ProcessorInfo;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 处理器信息响应类 {@code GetProcessorInfoResp}
* <p>
* 用于封装处理器信息的响应结果继承自 {@link BaseRespStatus}包含状态码消息以及处理器的详细信息
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>继承基础响应状态类统一包含状态码和消息</li>
* <li>封装处理器相关的详细信息通过 {@code cpuInfo} 字段返回</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>用于 RESTful API 的处理器信息查询接口</li>
* <li>适用于系统监控模块获取处理器的详细配置信息例如核心数线程数架构等</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "status": "SUCCESS",
* "message": "Processor information retrieved successfully",
* "cpuInfo": {
* "model": "Intel(R) Core(TM) i7-10700K",
* "cores": 8,
* "threads": 16,
* "architecture": "x86_64"
* }
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Schema(name = "处理器信息")
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@JsonPropertyOrder({"cpuInfo", "status", "message"})
@AllArgsConstructor
@NoArgsConstructor
public class GetProcessorInfoResp extends BaseRespStatus {
/**
* 处理器信息内容
* <p>
* 包含处理器的详细信息例如型号核心数线程数架构类型等
* 类型为 {@link ProcessorInfo}
* </p>
*/
@Schema(description = "处理器信息内容,例如型号、核心数、线程数、架构类型等。")
private ProcessorInfo cpuInfo;
}

View File

@ -0,0 +1,148 @@
package com.cmhi.magent.pojo.vo;
import com.cmhi.magent.common.CommonEnumHandler;
import com.cmhi.magent.common.ConstValue;
import com.cmhi.magent.common.ErrorCode;
import com.cmhi.magent.config.ProtocolConfigure;
import com.cmhi.magent.pojo.base.BaseProtocol;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/**
* 通用协议响应类 {@code ProtocolResp<T>}
* <p>
* 用于封装响应结果的通用协议格式继承自 {@link BaseProtocol}提供统一的协议结构支持
* 包括版本号加密类型时间戳状态码及消息内容
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>封装协议的基础字段包括版本号`ver`加密类型`cryptoType`时间戳`timeStamp`</li>
* <li>支持通过静态方法快速生成不同类型的响应对象</li>
* <li>可根据错误码和响应消息构造不同的响应内容</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>适用于与前端或外部系统的接口通信作为标准化的协议响应结构</li>
* <li>用于封装业务逻辑处理后的统一返回结果</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "ver": "1.0",
* "cryptoType": "AES",
* "timeStamp": 1673088000000,
* "code": 200,
* "msgContent": {
* "status": "SUCCESS",
* "message": "Operation completed successfully"
* }
* }
* </pre>
* </p>
*
* @param <T> 响应消息的泛型类型通常为业务数据或错误消息
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Slf4j
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"ver", "cryptoType", "timeStamp", "code", "msgContent"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProtocolResp<T> extends BaseProtocol<T> {
/**
* 状态码
* <p>
* 表示协议的响应状态码例如 HTTP 状态码200 表示成功400 表示请求错误等
* </p>
*/
@Schema(description = "状态码例如200 表示成功400 表示请求错误")
private Integer code;
/**
* 根据 HTTP 状态码和响应消息内容生成协议响应
*
* @param httpCode HTTP 状态码
* @param respMsg 响应消息内容
* @param <T> 响应消息的泛型类型
* @return 格式化的协议响应对象
*/
private static <T> ProtocolResp<T> result(Integer httpCode, T respMsg) {
ProtocolResp<T> resp = new ProtocolResp<>();
resp.setVer(ConstValue.Protocol.VERSION);
resp.setCode(httpCode);
resp.setCryptoType(ProtocolConfigure.SECURITY_PROTOCOL_TYPE);
resp.setTimeStamp(System.currentTimeMillis());
resp.setMsgContent(respMsg);
return resp;
}
/**
* 根据错误码和响应消息生成协议响应
*
* @param err 错误码
* @param respMsg 响应消息内容
* @param <T> 响应消息的泛型类型
* @return 格式化的协议响应对象
*/
public static <T> ProtocolResp<T> result(ErrorCode err, T respMsg) {
return result(err.getHttpCode(), respMsg);
}
/**
* 根据错误码生成协议响应响应消息为 {@link BaseRespStatus} 类型
*
* @param err 错误码
* @return 格式化的协议响应对象
*/
public static ProtocolResp<BaseRespStatus> result(ErrorCode err) {
BaseRespStatus rspMsg = new BaseRespStatus(err.getCode(), new String[] {err.getDescription()});
return result(err.getHttpCode(), rspMsg);
}
/**
* 根据响应消息生成协议响应响应消息需继承 {@link BaseRespStatus}
*
* @param respMsg 响应消息内容
* @param <T> 响应消息的具体类型需继承 {@link BaseRespStatus}
* @return 格式化的协议响应对象
*/
public static <T extends BaseRespStatus> ProtocolResp<T> result(T respMsg) {
return result(Objects.requireNonNull(CommonEnumHandler.codeOf(ErrorCode.class, respMsg.getStatus())), respMsg);
}
/**
* 根据错误码自定义 HTTP 状态码和响应消息生成协议响应
*
* @param errCode 错误码
* @param httpCode 自定义 HTTP 状态码
* @param message 响应消息内容
* @return 格式化的协议响应对象
*/
public static ProtocolResp<BaseRespStatus> result(ErrorCode errCode, Integer httpCode, String[] message) {
BaseRespStatus rspMsg = new BaseRespStatus(errCode.getCode(), message);
return result(httpCode, rspMsg);
}
}

View File

@ -0,0 +1,68 @@
package com.cmhi.magent.pojo.vo;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.po.VersionInfo;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 版本信息响应类 {@code VersionResp}
*
* 用于封装版本信息查询接口的响应结果继承自 {@link BaseRespStatus}包含状态码消息以及版本的详细信息
*
* <p>
* 核心功能
* <ul>
* <li>提供统一的接口响应结构继承自 {@link BaseRespStatus}</li>
* <li>封装版本的详细信息通过 {@link VersionInfo} 对象表示</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>应用于系统版本查询接口返回当前系统或模块的版本信息</li>
* <li>用于客户端与服务端保持一致性的版本校验场景</li>
* </ul>
* </p>
*
* <p>
* JSON 输出示例
* <pre>
* {
* "status": "SUCCESS",
* "message": "Version information retrieved successfully",
* "version": {
* "versionNumber": "1.0.0",
* "releaseDate": "2023-01-01",
* "description": "Initial release of the system."
* }
* }
* </pre>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@EqualsAndHashCode(callSuper = true)
@Data
@JsonPropertyOrder({"version", "status", "message"})
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class VersionResp extends BaseRespStatus {
/**
* 版本信息内容
*
* 表示当前系统或模块的版本详情信息包括版本号发布日期以及描述信息
*/
@Schema(description = "版本信息内容,包括版本号、发布日期以及描述信息")
private VersionInfo version;
}

View File

@ -0,0 +1,57 @@
package com.cmhi.magent.service;
import com.cmhi.magent.annotation.OperationLogAnnotation;
import com.cmhi.magent.common.ErrorCode;
import jakarta.servlet.http.HttpServletRequest;
/**
* 操作日志服务接口
* <p>
* 提供记录系统操作日志的方法用于跟踪系统中执行的操作支持基于注解或自定义参数的日志记录
* 该接口可与业务逻辑集成以满足审计问题排查或数据分析的需求
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public interface OperationLogService {
/**
* 记录系统操作日志基于注解方式
* <p>
* 使用 {@link OperationLogAnnotation} 注解中定义的元数据记录操作日志
* 通常用于通过切面AOP方式拦截调用而生成日志记录
* </p>
*
* @param call 调用的接口或方法名称
* @param request 当前的 HTTP 请求对象包含请求者的相关信息
* @param result 操作的结果对象用于记录操作的返回值
* @param annotation 操作日志注解包含元数据信息如模块操作类型等
* @param errorCode 错误码对象记录操作中可能出现的错误信息
*/
void systemOperationLog(String call, HttpServletRequest request, Object result, OperationLogAnnotation annotation, ErrorCode errorCode);
/**
* 记录系统操作日志基于自由参数方式
* <p>
* 手动指定模块操作类型及描述等信息来记录操作日志
* 通常用于需要更灵活的日志记录场景
* </p>
*
* @param call 调用的接口或方法名称
* @param request 当前的 HTTP 请求对象包含请求者的相关信息
* @param result 操作的结果对象用于记录操作的返回值
* @param module 操作所属模块用于分类日志记录例如 "用户管理""系统设置"
* @param optType 操作类型标识操作的具体类别例如 "新增""修改""删除"
* @param desc 操作描述详细说明本次操作的内容或目的
* @param errorCode 错误码对象记录操作中可能出现的错误信息
*/
void systemOperationLog(String call,
HttpServletRequest request,
Object result,
String module,
String optType,
String desc,
ErrorCode errorCode);
}

View File

@ -0,0 +1,70 @@
package com.cmhi.magent.service;
import com.cmhi.magent.crypto.DecryptRequestProtocol;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.http.HttpInputMessage;
import java.io.IOException;
/**
* 协议安全服务接口
* <p>
* 提供协议的加密与解密功能适用于处理敏感数据的场景
* 该接口定义了加密和解密方法支持字符串和对象的协议加密与解密
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public interface ProtocolSecurityService {
/**
* 解密协议字符串
* <p>
* 将密文协议字符串解密为明文字符串
* </p>
*
* @param ciphertext 需要解密的协议密文
* @return 解密后的明文字符串
* @throws JsonProcessingException 如果在解析过程中发生 JSON 处理错误
*/
String decryptProtocol(String ciphertext) throws JsonProcessingException;
/**
* HTTP 输入消息中解密协议
* <p>
* 解析并解密传入的 HTTP 请求消息返回对应的协议对象
* </p>
*
* @param httpInputMessage 需要解密的 HTTP 输入消息
* @return 解密后的协议对象 {@link DecryptRequestProtocol}
* @throws IOException 如果在读取输入流时发生 I/O 错误
*/
DecryptRequestProtocol decryptProtocol(HttpInputMessage httpInputMessage) throws IOException;
/**
* 加密明文字符串协议
* <p>
* 将明文字符串加密为密文支持不同的加密类型
* </p>
*
* @param plainText 明文字符串
* @param cryptoType 加密类型用于指定加密算法或方式
* @return 加密后的密文字符串
*/
String encryptProtocolString(String plainText, int cryptoType);
/**
* 加密协议对象
* <p>
* 将对象协议加密为密文字符串并封装为 {@link ProtocolResp} 对象
* </p>
*
* @param orgProtocol 原始协议对象
* @param cryptoType 加密类型用于指定加密算法或方式
* @return 加密后的协议响应对象包含密文字符串
*/
ProtocolResp<String> encryptProtocol(Object orgProtocol, int cryptoType);
}

View File

@ -0,0 +1,58 @@
package com.cmhi.magent.service;
import com.cmhi.magent.pojo.po.HwInfo;
import com.cmhi.magent.pojo.po.ProcessorInfo;
/**
* 系统信息服务接口
* <p>
* 提供获取操作系统信息硬件信息和处理器信息的功能
* 该接口定义了系统信息的查询方法便于实现类对相关数据进行封装和提供
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public interface SystemInfoService {
/**
* 获取操作系统的名称
* <p>
* 返回当前系统的操作系统类型或名称例如 "Windows 10" "Ubuntu 20.04"
* </p>
*
* @return 操作系统名称
*/
String getOsName();
/**
* 获取操作系统的启动时间戳
* <p>
* 返回自1970年1月1日00:00:00 UTC以来的毫秒数表示当前操作系统的启动时间
* </p>
*
* @return 操作系统启动时间戳
*/
long getOsBootTimeStamp();
/**
* 获取处理器信息
* <p>
* 封装处理器的相关信息如核心数量线程数量频率等结果以 {@link ProcessorInfo} 对象形式返回
* </p>
*
* @return 处理器信息对象
*/
ProcessorInfo getProcessInfo();
/**
* 获取硬件信息
* <p>
* 包含硬件相关的详细信息例如内存大小磁盘容量等结果以 {@link HwInfo} 对象形式返回
* </p>
*
* @return 硬件信息对象
*/
HwInfo getHardwareInfo();
}

View File

@ -0,0 +1,170 @@
package com.cmhi.magent.service.impl;
import com.cmhi.magent.annotation.OperationLogAnnotation;
import com.cmhi.magent.common.ErrorCode;
import com.cmhi.magent.config.CommonConfigure;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.HelperUtils;
import com.cmhi.magent.pojo.po.ApiContext;
import com.cmhi.magent.pojo.po.OperationLog;
import com.cmhi.magent.service.OperationLogService;
import com.jayway.jsonpath.JsonPath;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Optional;
/**
* 实现了 {@link OperationLogService} 接口的操作日志服务实现类
* <p>
* 该类提供了系统操作日志的记录功能可以通过注解方式或手动指定参数的方式记录操作日志
* 日志内容包括调用的方法名称操作所属模块操作类型HTTP 请求相关信息返回结果操作时间延时等
* </p>
* <p>
* 此服务支持将操作日志记录到持久化存储或其他日志存储系统中方便系统审计问题排查或数据分析
* </p>
* <p>
* 如果在记录日志过程中出现异常会被捕获并安全忽略确保日志记录不会影响正常业务流程
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Service
@Slf4j
public class OperationLogServiceImpl implements OperationLogService {
/**
* 数据库字段最大长度用于截取过长的字符串避免存储溢出
*/
private static final int MAX_DB_STRING = 4090;
/**
* 用于排除记录错误处理路径的请求日志
*/
private static final String ERROR_URL = "/error";
/**
* Spring Boot 提供的服务器属性配置用于获取上下文路径等信息
*/
@Resource
private ServerProperties serverProperties;
/**
* 基于注解记录操作日志
* <p>
* 当注解不为空时会从 {@link OperationLogAnnotation} 中提取模块操作类型和描述信息调用另一个重载方法进行日志记录
* 如果注解为空则使用默认空值调用重载方法
* </p>
*
* @param call 调用的方法名称或接口名称
* @param request 当前的 HTTP 请求对象包含请求者的相关信息
* @param result 操作结果对象用于记录方法返回值
* @param annotation 操作日志注解包含模块操作类型和描述信息
* @param errorCode 错误码对象记录操作中出现的错误信息
*/
@Override
public void systemOperationLog(String call,
HttpServletRequest request,
Object result,
OperationLogAnnotation annotation,
ErrorCode errorCode) {
if (annotation != null) {
// 提取注解中的元数据并记录日志
systemOperationLog(call, request, result, annotation.OperationModule(), annotation.OperationType(), annotation.OperationDesc(),
errorCode);
} else {
// 如果注解为空使用默认值记录日志
systemOperationLog(call, request, result, "", "", "", errorCode);
}
}
/**
* 基于自由参数记录操作日志
* <p>
* 手动指定操作所属模块操作类型描述及其他相关信息生成操作日志并记录
* 日志内容包括
* <ul>
* <li>模块操作类型和描述信息</li>
* <li>HTTP 请求的相关信息方法路径请求头IP 地址请求体</li>
* <li>返回结果操作时间和延迟信息</li>
* </ul>
* 方法包含异常安全处理如果日志记录过程中出现异常将会被捕获并安全忽略
* </p>
*
* @param call 调用的方法名称或接口名称
* @param request 当前的 HTTP 请求对象包含请求者的相关信息
* @param result 操作结果对象用于记录方法返回值
* @param module 操作所属模块的名称例如 "用户管理""系统设置"
* @param optType 操作类型例如 "新增""修改""删除"
* @param desc 操作描述对操作的内容或目的的详细说明
* @param errorCode 错误码对象记录操作中出现的错误信息
*/
@Override
public void systemOperationLog(String call,
HttpServletRequest request,
Object result,
String module,
String optType,
String desc,
ErrorCode errorCode) {
try {
// 获取当前请求的上下文信息
ApiContext ctx = ApiContextUtils.get();
OperationLog optLog = new OperationLog();
// 排除错误处理路径的日志记录
if (request.getRequestURI().startsWith(CommonConfigure.PROJECT_PREFIX_URL + ERROR_URL)) {
return;
}
// 设置操作日志的基础信息
optLog.setOperationStatus(errorCode.getStringValue());
optLog.setCallFunction(call);
optLog.setModule(module);
optLog.setOperationType(optType);
optLog.setDescription(desc);
// 设置操作时间为当前系统时间
optLog.setOperationTime(new Timestamp(System.currentTimeMillis()));
// 设置 HTTP 请求信息
optLog.setHttpMethod(request.getMethod());
String prefixUrl = Optional.ofNullable(serverProperties.getServlet().getContextPath()).orElse("");
String url = request.getRequestURI().replaceFirst("^" + prefixUrl, "");
optLog.setHttpPath(url);
// 设置请求 IP 地址
optLog.setRequestIp(HelperUtils.getHttpRequestSrcIp(request));
// 设置操作结果截取过长字符串以避免数据库存储溢出
optLog.setResult(HelperUtils.truncateString(HelperUtils.getJson(result), MAX_DB_STRING));
// 设置请求头信息
optLog.setRequestHeaders(HelperUtils.truncateString(HelperUtils.getHttpRequestHeaders(request), MAX_DB_STRING));
// 设置请求体信息
String reqJson = ctx.getRequestBody();
optLog.setRequest(HelperUtils.truncateString(reqJson, MAX_DB_STRING));
// 记录操作响应时间
optLog.setExpendTime(System.currentTimeMillis() - ctx.getRequestTime());
// 记录传输延迟如果请求体中包含时间戳信息
if (HelperUtils.stringNotEmptyOrNull(reqJson)) {
Long reqTime = Optional.ofNullable((Long) JsonPath.read(reqJson, "$.timeStamp")).orElse(0L);
optLog.setTransmitDelay(ctx.getRequestTime() - reqTime);
}
// 保存日志此处为打印到控制台
log.info("{}", HelperUtils.getJson(optLog));
} catch (Exception ignored) {
// 忽略所有异常避免日志记录影响正常业务流程
}
}
}

View File

@ -0,0 +1,232 @@
package com.cmhi.magent.service.impl;
import com.cmhi.magent.common.ConstValue;
import com.cmhi.magent.common.ErrorCode;
import com.cmhi.magent.common.ProtoCryptoType;
import com.cmhi.magent.config.ProtocolConfigure;
import com.cmhi.magent.crypto.DecryptRequestProtocol;
import com.cmhi.magent.crypto.arithmetic.CryptoHelper;
import com.cmhi.magent.exception.SecurityProtocolException;
import com.cmhi.magent.misc.HelperUtils;
import com.cmhi.magent.pojo.dto.ProtocolReq;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.cmhi.magent.service.ProtocolSecurityService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* 协议安全服务实现类
* <p>
* 提供对请求和响应协议的加密与解密功能支持多种加密方式 BASE64AES128AES256 DES
* 主要用于处理安全协议确保传输数据的安全性
* </p>
* <p>
* 该服务适用于客户端与服务端之间的加解密通信支持动态选择加密类型
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Service
@Slf4j
public class ProtocolSecurityServiceImpl implements ProtocolSecurityService {
/**
* 用于 JSON 解析和序列化/反序列化
*/
@Resource
private ObjectMapper objectMapper;
/**
* 协议配置类用于获取密钥等配置
*/
@Resource
private ProtocolConfigure protocolConfigure;
/**
* 解密协议
* <p>
* 根据加密类型cryptoType对协议内容进行解密支持的加密类型包括
* <ul>
* <li>CRYPTO_NONE (0)未加密直接返回原始内容</li>
* <li>CRYPTO_BASE64 (1)Base64 解码</li>
* <li>CRYPTO_AES128 (2)AES128 解密</li>
* <li>CRYPTO_AES256 (3)AES256 解密</li>
* <li>CRYPTO_DES (4)DES 解密</li>
* </ul>
* 如果加密类型超出支持范围或解密失败将抛出 {@link SecurityProtocolException}
* </p>
*
* @param ciphertext 加密的协议内容JSON 格式字符串
* @return 解密后的协议内容JSON 格式字符串
* @throws JsonProcessingException 如果解析 JSON 失败
* @throws SecurityProtocolException 如果解密失败或加密类型不支持
*/
@Override
public String decryptProtocol(String ciphertext) throws JsonProcessingException {
JsonNode objRoot = objectMapper.readTree(ciphertext);
int cryptoType = objRoot.path("cryptoType").asInt();
// 协议未加密
if (cryptoType == ProtoCryptoType.CRYPTO_NONE.getValue()) {
return ciphertext;
}
if (cryptoType > ProtoCryptoType.CRYPTO_AES256.getValue() || cryptoType < ProtoCryptoType.CRYPTO_NONE.getValue()) {
throw new SecurityProtocolException(ErrorCode.ERR_PARAMS, "cryptoType 字段取值为 [0, 4]");
}
ProtocolReq<String> proReq = objectMapper.readValue(ciphertext, new TypeReference<>() {
});
byte[] base64Decode = CryptoHelper.base64Decryption(proReq.getMsgContent());
byte[] decryptContent;
if (Objects.equals(proReq.getCryptoType(), ProtoCryptoType.CRYPTO_BASE64.getValue())) {
decryptContent = base64Decode;
} else if (Objects.equals(proReq.getCryptoType(), ProtoCryptoType.CRYPTO_AES128.getValue())) {
try {
decryptContent = CryptoHelper.aes128Decryption(base64Decode, protocolConfigure.getCryptoKey());
} catch (Exception e) {
log.error("AES128 decode message error: {}", base64Decode);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_AES128);
}
} else if (Objects.equals(proReq.getCryptoType(), ProtoCryptoType.CRYPTO_AES256.getValue())) {
try {
decryptContent = CryptoHelper.aes256Decryption(base64Decode, protocolConfigure.getCryptoKey());
} catch (Exception e) {
log.error("AES256 decode message error: {}", base64Decode);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_AES256);
}
} else if (Objects.equals(proReq.getCryptoType(), ProtoCryptoType.CRYPTO_DES.getValue())) {
try {
decryptContent = CryptoHelper.desDecryption(base64Decode, protocolConfigure.getCryptoKey());
} catch (Exception e) {
log.error("DES decode message error: {}", base64Decode);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_3DES);
}
} else {
log.error("Unknown protocol security type: {}, {}", proReq.getCryptoType(), ciphertext);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_UNKNOWN);
}
String decodeMsg = new String(decryptContent, StandardCharsets.UTF_8);
return ciphertext.replace("\"" + proReq.getMsgContent() + "\"", decodeMsg);
}
/**
* 解密协议重载方法
* <p>
* {@link HttpInputMessage} 中提取协议内容并进行解密处理
* </p>
*
* @param httpInputMessage 包含协议内容的 HTTP 输入消息
* @return 解密后的协议对象 {@link DecryptRequestProtocol}
* @throws IOException 如果读取请求内容失败
*/
@Override
public DecryptRequestProtocol decryptProtocol(HttpInputMessage httpInputMessage) throws IOException {
String reqMessage = IOUtils.toString(httpInputMessage.getBody(), StandardCharsets.UTF_8);
return new DecryptRequestProtocol(httpInputMessage, decryptProtocol(reqMessage));
}
/**
* 加密协议内容
* <p>
* 根据指定的加密类型对明文内容进行加密支持的加密类型包括 Base64AES128AES256 DES
* </p>
*
* @param plainText 明文内容
* @param cryptoType 加密类型
* @return 加密后的内容
* @throws SecurityProtocolException 如果加密失败或加密类型不支持
*/
@Override
public String encryptProtocolString(String plainText, int cryptoType) {
String cipherText;
if (cryptoType == ProtoCryptoType.CRYPTO_BASE64.getValue()) {
cipherText = CryptoHelper.base64Encryption(plainText.getBytes(StandardCharsets.UTF_8));
} else if (cryptoType == ProtoCryptoType.CRYPTO_AES128.getValue()) {
try {
byte[] encode = CryptoHelper.aes128Encryption(plainText.getBytes(StandardCharsets.UTF_8), protocolConfigure.getCryptoKey());
cipherText = CryptoHelper.base64Encryption(encode);
} catch (Exception e) {
log.error("AES128 encode message error({}): {}", e.getMessage(), plainText);
throw new SecurityProtocolException(ErrorCode.ERR_ENCRYPT_AES128, e.getMessage());
}
} else if (cryptoType == ProtoCryptoType.CRYPTO_AES256.getValue()) {
try {
byte[] encode = CryptoHelper.aes256Encryption(plainText.getBytes(StandardCharsets.UTF_8), protocolConfigure.getCryptoKey());
cipherText = CryptoHelper.base64Encryption(encode);
} catch (Exception e) {
log.error("AES256 encode message error({}): {}", e.getMessage(), plainText);
throw new SecurityProtocolException(ErrorCode.ERR_ENCRYPT_AES256, e.getMessage());
}
} else if (cryptoType == ProtoCryptoType.CRYPTO_DES.getValue()) {
try {
byte[] encode = CryptoHelper.desEncryption(plainText.getBytes(StandardCharsets.UTF_8), protocolConfigure.getCryptoKey());
cipherText = CryptoHelper.base64Encryption(encode);
} catch (Exception e) {
log.error("DES encode message error({}): {}", e.getMessage(), plainText);
throw new SecurityProtocolException(ErrorCode.ERR_ENCRYPT_3DES, e.getMessage());
}
} else {
log.error("Unknown protocol security type: {}, {}.", cryptoType, plainText);
throw new SecurityProtocolException(ErrorCode.ERR_ENCRYPT_UNKNOWN);
}
return cipherText;
}
/**
* 加密协议对象
* <p>
* 将协议对象的内容加密后封装为 {@link ProtocolResp} 对象
* </p>
*
* @param orgProtocol 原始协议对象
* @param cryptoType 加密类型
* @return 加密后的协议对象
* @throws SecurityProtocolException 如果加密失败
*/
@Override
public ProtocolResp<String> encryptProtocol(Object orgProtocol, int cryptoType) {
ProtocolResp<String> cryptoObject = new ProtocolResp<>();
cryptoObject.setVer(ConstValue.Protocol.VERSION);
cryptoObject.setCryptoType(ProtocolConfigure.SECURITY_PROTOCOL_TYPE);
cryptoObject.setTimeStamp(System.currentTimeMillis());
String msgContentJsonString;
try {
Method getMsgMethod = orgProtocol.getClass().getMethod("getMsgContent");
Method getCode = orgProtocol.getClass().getMethod("getCode");
msgContentJsonString = HelperUtils.getJson(getMsgMethod.invoke(orgProtocol));
cryptoObject.setCode((Integer) getCode.invoke(orgProtocol));
} catch (Exception e) {
log.error("Json encode message error: {}", orgProtocol);
throw new SecurityProtocolException(ErrorCode.ERR_SYSTEMEXCEPTION);
}
cryptoObject.setMsgContent(encryptProtocolString(msgContentJsonString, cryptoType));
return cryptoObject;
}
}

View File

@ -0,0 +1,98 @@
package com.cmhi.magent.service.impl;
import com.cmhi.magent.pojo.mapper.IObjectConvert;
import com.cmhi.magent.pojo.po.HwInfo;
import com.cmhi.magent.pojo.po.ProcessorInfo;
import com.cmhi.magent.service.SystemInfoService;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.ComputerSystem;
/**
* 系统信息服务实现类
* <p>
* 该服务通过集成 OSHI一个跨平台的硬件/操作系统信息库来提供系统的相关信息包括操作系统名称系统启动时间
* 处理器信息以及硬件信息
* </p>
* <p>
* 主要功能包括
* <ul>
* <li>获取操作系统名称</li>
* <li>获取系统启动时间时间戳</li>
* <li>获取处理器详细信息</li>
* <li>获取硬件系统信息如主板硬件厂商等</li>
* </ul>
* </p>
* <p>
* 该类实现了 {@link SystemInfoService} 接口使用 OSHI 库实现底层系统信息的收集
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Service
public class SystemInfoServiceImpl implements SystemInfoService {
/**
* OSHI {@link SystemInfo} 类实例用于获取操作系统和硬件相关信息
*/
private final SystemInfo si = new SystemInfo();
/**
* 获取操作系统名称
* <p>
* 通过 OSHI 库获取操作系统的名称和其它描述信息
* </p>
*
* @return 操作系统的名称
*/
@Override
public String getOsName() {
return String.valueOf(si.getOperatingSystem());
}
/**
* 获取系统启动时间的时间戳
* <p>
* 通过 OSHI 库计算系统的启动时间秒级时间戳转换为毫秒级时间戳
* </p>
*
* @return 系统启动时间的时间戳以毫秒为单位
*/
@Override
public long getOsBootTimeStamp() {
return si.getOperatingSystem().getSystemBootTime() * 1000L;
}
/**
* 获取处理器信息
* <p>
* 通过 OSHI 库获取处理器的详细信息包括逻辑处理器数处理器标识符等
* 并通过 {@link IObjectConvert} 工具类将其转换为自定义的 {@link ProcessorInfo} 对象
* </p>
*
* @return 包含处理器详细信息的 {@link ProcessorInfo} 对象
*/
@Override
public ProcessorInfo getProcessInfo() {
CentralProcessor proc = si.getHardware().getProcessor();
return IObjectConvert.INSTANCE.toProcessorInfo(proc, proc.getProcessorIdentifier());
}
/**
* 获取硬件信息
* <p>
* 通过 OSHI 库获取硬件系统的基本信息包括主板型号硬件厂商等
* 并通过 {@link IObjectConvert} 工具类将其转换为自定义的 {@link HwInfo} 对象
* </p>
*
* @return 包含硬件详细信息的 {@link HwInfo} 对象
*/
@Override
public HwInfo getHardwareInfo() {
ComputerSystem cs = si.getHardware().getComputerSystem();
return IObjectConvert.INSTANCE.toHwInfo(cs);
}
}

View File

@ -0,0 +1,41 @@
package com.cmhi.magent.setup;
import com.cmhi.magent.config.CommonConfigure;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* 系统初始化类用于在应用程序启动后执行必要的初始化逻辑<p>
* 该类实现了 {@link CommandLineRunner} 接口可在 Spring Boot 应用程序启动完成后运行指定的代码<p>
* 当前实现的主要功能是打印在线 API 文档的访问地址<p>
* 使用 {@code @Slf4j} 注解简化日志记录功能
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Component
@Slf4j
public class SystemInitial implements CommandLineRunner {
/**
* 在应用程序启动后执行初始化逻辑<p>
* 该方法会被 Spring Boot 自动调用用于执行自定义的启动后任务<p>
* 当前实现功能是调用 {@link #showOpenApiUrl()} 方法来打印 API 文档访问地址
* @param args 应用程序启动时传递的参数列表
*/
@Override
public void run(String... args) {
showOpenApiUrl();
}
/**
* 打印在线 API 文档的访问地址到日志<p>
* 该方法使用日志记录功能将 Swagger 文档的访问地址输出到日志中地址格式为<pre>{BASEURL}/swagger-ui/index.html</pre><p>
* 其中基础 URL 通过 {@link CommonConfigure#BASEURL} 变量获取
* @implNote 使用 {@code log.info()} 方法输出日志信息
*/
private void showOpenApiUrl() {
log.info("Access Online API Documents: {}/swagger-ui/index.html", CommonConfigure.BASEURL);
}
}

View File

@ -0,0 +1,128 @@
package com.cmhi.magent.validation.group;
/**
* 验证分组接口定义
* <p>
* 该接口用于定义数据校验时的分组标识结合 Bean Validation {@code @Validated} 注解使用
* 可实现分组校验逻辑便于在不同场景下复用校验规则
* </p>
* <p>
* 按照业务需求分组校验规则从通用协议校验组 {@link ProtocolCommonValid} 开始
* 逐步扩展到更具体的场景例如登录请求登出请求用户请求操作日志请求等
* 接口的继承关系表示了校验规则的层次结构
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public interface ValidGroups {
/**
* 通用协议校验分组
* <p>
* 包含所有协议相关的基本校验规则可作为其他分组校验规则的基础接口
* </p>
*/
interface ProtocolCommonValid {
}
/**
* 基础协议校验分组
* <p>
* 扩展了通用协议校验规则适用于需要基础协议校验的场景
* </p>
*/
interface BaseProtocolValid extends ProtocolCommonValid {
}
/**
* 分页请求校验分组
* <p>
* 适用于具有分页功能的请求校验继承了基础协议校验规则
* </p>
*/
interface BasePagedReqValid extends BaseProtocolValid {
}
/**
* 登录请求校验分组
* <p>
* 适用于登录功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface LoginReqValid extends BaseProtocolValid {
}
/**
* 登出请求校验分组
* <p>
* 适用于登出功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface LogoutReqValid extends BaseProtocolValid {
}
/**
* 操作日志请求校验分组
* <p>
* 适用于操作日志功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface OperationLogReqValid extends BaseProtocolValid {
}
/**
* 用户 ID 请求校验分组
* <p>
* 适用于用户 ID 请求的校验规则继承了基础协议校验规则
* </p>
*/
interface UserIdReqValid extends BaseProtocolValid {
}
/**
* 用户请求校验分组
* <p>
* 适用于用户功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface UserReqValid extends BaseProtocolValid {
}
/**
* 资源请求校验分组
* <p>
* 适用于资源管理相关功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface ResourceReqValid extends BaseProtocolValid {
}
/**
* 资源信息校验分组
* <p>
* 适用于资源信息相关功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface ResourceInfoValid extends BaseProtocolValid {
}
/**
* 字典请求校验分组
* <p>
* 适用于字典功能请求的校验规则继承了基础协议校验规则
* </p>
*/
interface DictReqValid extends BaseProtocolValid {
}
/**
* 字典请求内容校验分组
* <p>
* 适用于字典请求内容的校验规则继承了基础协议校验规则
* </p>
*/
interface DictReqContentValid extends BaseProtocolValid {
}
}

View File

@ -0,0 +1,62 @@
package com.cmhi.magent.validation.valids;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.Pattern;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义校验注解用于验证字段是否符合 UUID 格式
* <p>
* 该注解基于正则表达式实现校验支持标准的 UUID 格式例如8-4-4-4-12 的格式
* </p>
* <p>
* 正则表达式规则
* <ul>
* <li>32 个小写十六进制字符按以下模式分隔8-4-4-4-12</li>
* <li> 3 段的第一个字符为 1-5表示 UUID 版本</li>
* <li> 4 段的第一个字符为 89a b表示 UUID 的变种</li>
* </ul>
* </p>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Target(ElementType.FIELD)
@Constraint(validatedBy = {})
@Retention(RetentionPolicy.RUNTIME)
@Pattern(regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
public @interface UUID {
/**
* 错误消息当字段值不符合 UUID 格式时返回
*
* @return 默认错误消息支持自定义
*/
String message() default "{invalid.uuid}";
/**
* 指定校验分组
* <p>
* 可用于分组校验场景例如在不同的业务逻辑中使用不同的校验规则
* </p>
*
* @return 校验分组默认为空
*/
Class<?>[] groups() default {};
/**
* 用于携带元数据信息的扩展
* <p>
* 通常用于自定义校验逻辑时传递附加信息
* </p>
*
* @return 负载类型数组
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,72 @@
package com.cmhi.magent.validation.valids;
import com.cmhi.magent.validation.valids.impl.ValidHttpMethodImpl;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义校验注解用于验证字段或参数是否为有效的 HTTP 方法
* <p>
* 该注解适用于字段或方法参数校验逻辑通过 {@link ValidHttpMethodImpl} 实现
* </p>
* <p>
* 可用于确保字段或参数值为标准的 HTTP 请求方法例如GETPOSTPUTDELETE
* </p>
*
* <p>
* 相关参数说明
* <ul>
* <li><b>message</b>校验失败时返回的错误消息默认为空字符串可自定义</li>
* <li><b>groups</b>指定校验分组用于分组校验逻辑</li>
* <li><b>payload</b>扩展字段用于携带元数据信息</li>
* </ul>
* </p>
*
* <p>
* 默认的实现类{@link ValidHttpMethodImpl}
* 该实现通常包含自定义逻辑确保输入值属于预定义的 HTTP 方法集合
* </p>
*
* @author
* @version 1.0.0
* @since 2025-01-07
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ValidHttpMethodImpl.class})
public @interface ValidHttpMethod {
/**
* 错误消息当字段或参数值不为有效的 HTTP 方法时返回
*
* @return 默认错误消息支持自定义
*/
String message() default "";
/**
* 指定校验分组
* <p>
* 可用于分组校验场景例如在不同的业务逻辑中使用不同的校验规则
* </p>
*
* @return 校验分组默认为空
*/
Class<?>[] groups() default {};
/**
* 用于携带元数据信息的扩展字段
* <p>
* 通常用于自定义校验逻辑时传递附加信息
* </p>
*
* @return 负载类型数组
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,80 @@
package com.cmhi.magent.validation.valids;
import com.cmhi.magent.validation.valids.impl.ValidPageSizeImpl;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义校验注解用于验证分页查询时的页大小Page Size是否有效
* <p>
* 该注解适用于字段或方法参数校验逻辑由 {@link ValidPageSizeImpl} 实现
* </p>
* <p>
* 通常用于限制分页参数的范围确保页大小符合系统的限制要求例如页大小应在 1 到某个最大值之间
* </p>
*
* <p>
* 相关参数说明
* <ul>
* <li><b>message</b>校验失败时返回的错误消息</li>
* <li><b>groups</b>指定校验分组适配不同场景的校验逻辑</li>
* <li><b>payload</b>扩展字段用于携带校验的元数据信息</li>
* </ul>
* </p>
*
* <p>
* 默认的实现类{@link ValidPageSizeImpl}
* 该实现类通常包含用于校验页大小范围的具体逻辑比如最小页大小为 1最大页大小为 100 或其他系统配置值
* </p>
*
* <p>
* 使用示例
* <pre>
* &#64;ValidPageSize(message = "Page size must be between 1 and 100")
* private Integer pageSize;
* </pre>
* </p>
*
* @author
* @version 1.0.0
* @since 2025-01-07
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ValidPageSizeImpl.class})
public @interface ValidPageSize {
/**
* 错误消息当页大小Page Size无效时返回
*
* @return 默认错误消息支持自定义
*/
String message();
/**
* 指定校验分组
* <p>
* 可用于分组校验例如在不同业务场景中应用不同的校验规则
* </p>
*
* @return 校验分组默认为空
*/
Class<?>[] groups() default {};
/**
* 用于携带元数据信息的扩展字段
* <p>
* 通常用于高级校验逻辑或特定场景下的附加信息传递
* </p>
*
* @return 负载类型数组
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,75 @@
package com.cmhi.magent.validation.valids;
import com.cmhi.magent.validation.valids.impl.ValidProtocolTimestampImpl;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义校验注解用于验证协议中的时间戳Protocol Timestamp是否符合规定的格式和范围
* <p>
* 该注解适用于字段或方法参数校验逻辑由 {@link ValidProtocolTimestampImpl} 实现
* </p>
* <p>
* 通常应用于需要验证时间戳的场景例如请求中的时间戳字段确保时间戳符合业务规则如格式正确未过期等
* </p>
*
* <p>
* 相关参数说明
* <ul>
* <li><b>message</b>校验失败时返回的错误消息</li>
* <li><b>groups</b>指定校验分组适配不同场景的校验逻辑</li>
* <li><b>payload</b>扩展字段用于携带校验的元数据信息</li>
* </ul>
* </p>
*
* <p>
* 默认的实现类{@link ValidProtocolTimestampImpl}
* 该实现类通常包含以下校验逻辑
* <ul>
* <li>验证时间戳的格式是否符合 ISO 8601 或其他指定格式</li>
* <li>校验时间戳是否在允许的时间范围内例如不能早于一定时间也不能超出当前时间</li>
* </ul>
* </p>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ValidProtocolTimestampImpl.class})
public @interface ValidProtocolTimestamp {
/**
* 错误消息当时间戳Timestamp无效时返回
*
* @return 默认错误消息支持自定义
*/
String message();
/**
* 指定校验分组
* <p>
* 可用于分组校验例如在不同业务场景中应用不同的校验规则
* </p>
*
* @return 校验分组默认为空
*/
Class<?>[] groups() default {};
/**
* 用于携带元数据信息的扩展字段
* <p>
* 通常用于高级校验逻辑或特定场景下的附加信息传递
* </p>
*
* @return 负载类型数组
*/
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,93 @@
package com.cmhi.magent.validation.valids.impl;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.MessageUtil;
import com.cmhi.magent.validation.valids.ValidHttpMethod;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* 实现类 `ValidHttpMethodImpl`用于校验被 {@link ValidHttpMethod} 注解标注的字段中包含的 HTTP 方法是否合法
* <p>
* 该类实现了 {@link ConstraintValidator} 接口通过其 `isValid` 方法实现自定义校验逻辑
* 主要服务于验证 HTTP 请求方法确保字段值 `GET``POST` 符合标准的 HTTP 方法规范
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>验证传入的 HTTP 方法列表是否有效</li>
* <li>提供详细的校验错误消息包括无效方法及原因</li>
* <li>支持多语言错误提示提升用户体验</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>动态路由配置校验配置中 HTTP 方法是否合法</li>
* <li>API 参数校验确保客户端传入的 HTTP 方法符合约定</li>
* <li>安全性检查防止非法或拼写错误的 HTTP 方法导致异常</li>
* </ul>
* </p>
*
* @see ValidHttpMethod
* @see ConstraintValidator
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class ValidHttpMethodImpl implements ConstraintValidator<ValidHttpMethod, List<String>> {
/**
* 初始化校验器的方法
* <p>
* 默认实现中调用了父类的初始化逻辑可根据需要进行扩展
* </p>
*
* @param constraintAnnotation {@code @ValidHttpMethod} 注解包含相关参数信息
*/
@Override
public void initialize(ValidHttpMethod constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
/**
* 核心校验逻辑判断传入的 HTTP 方法列表是否包含无效的方法
* <p>
* 遍历列表中的每个方法尝试通过 {@link RequestMethod#resolve(String)} 方法解析
* 如果解析结果为 {@code null}则判定该方法无效并记录错误信息
* 如果存在无效方法校验失败并返回详细的错误消息若所有方法均有效则校验通过
* </p>
*
* @param strings 需要校验的 HTTP 方法列表
* @param context 校验器上下文用于构建错误消息
* @return {@code true} 表示校验通过{@code false} 表示校验失败
*/
@Override
public boolean isValid(List<String> strings, ConstraintValidatorContext context) {
StringBuilder errMsg = new StringBuilder();
// 遍历 HTTP 方法列表并校验
strings.forEach(k -> {
if (RequestMethod.resolve(k.toUpperCase()) == null) {
errMsg.append("[")
.append(k)
.append("]: ")
.append(MessageUtil.get("http.request_invalid", ApiContextUtils.getLanguage()));
}
});
// 如果存在错误消息校验失败
if (!errMsg.isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errMsg.toString()).addConstraintViolation();
return false;
}
// 校验成功
return true;
}
}

View File

@ -0,0 +1,67 @@
package com.cmhi.magent.validation.valids.impl;
import com.cmhi.magent.validation.valids.ValidPageSize;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* 实现类 {@code ValidPageSizeImpl}用于校验被 {@link ValidPageSize} 注解标注的字段值是否为有效的分页大小
* <p>
* 该类实现了 {@link ConstraintValidator} 接口通过其 `isValid` 方法实现自定义校验逻辑
* 核心功能是验证给定的分页大小是否符合业务规则例如是否为 5 的倍数
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>校验分页大小是否为合法值</li>
* <li>支持动态注解配置如可扩展为配置化规则</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>分页 API 的参数校验确保客户端传递的分页大小符合规定</li>
* <li>限制分页大小为标准化值避免前端传递异常分页参数</li>
* </ul>
* </p>
*
* @see ValidPageSize
* @see ConstraintValidator
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class ValidPageSizeImpl implements ConstraintValidator<ValidPageSize, Long> {
/**
* 初始化校验器的方法
* <p>
* 默认实现中调用了父类的初始化逻辑目前无需特殊初始化步骤
* 如果需要从注解中提取参数可在此方法中实现
* </p>
*
* @param constraintAnnotation {@code @ValidPageSize} 注解包含相关参数信息
*/
@Override
public void initialize(ValidPageSize constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
/**
* 校验分页大小是否为有效值
* <p>
* 业务规则分页大小必须为 5 的倍数若值符合规则返回 {@code true}否则返回 {@code false}
* </p>
*
* @param pageSize 分页大小{@code Long} 类型由注解标注的字段传入
* @param constraintValidatorContext 校验器上下文用于构建动态错误消息当前未使用
* @return {@code true} 表示校验通过{@code false} 表示校验失败
*/
@Override
public boolean isValid(Long pageSize, ConstraintValidatorContext constraintValidatorContext) {
// 校验分页大小是否为 5 的倍数
return pageSize != null && pageSize % 5 == 0;
}
}

View File

@ -0,0 +1,119 @@
package com.cmhi.magent.validation.valids.impl;
import com.cmhi.magent.common.ConstValue;
import com.cmhi.magent.config.ProtocolConfigure;
import com.cmhi.magent.misc.ApiContextUtils;
import com.cmhi.magent.misc.MessageUtil;
import com.cmhi.magent.validation.valids.ValidProtocolTimestamp;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.text.SimpleDateFormat;
/**
* 实现类 {@code ValidProtocolTimestampImpl}用于校验被 {@link ValidProtocolTimestamp} 注解标注的时间戳是否符合协议要求
* <p>
* 该类实现了 {@link ConstraintValidator} 接口通过其 `isValid` 方法实现自定义校验逻辑
* 核心功能是验证时间戳的合法性包括以下规则
* <ul>
* <li>时间戳不能晚于当前时间</li>
* <li>时间戳与当前时间的差距不能超过设置的超时时间</li>
* </ul>
* </p>
*
* <p>
* 核心功能
* <ul>
* <li>校验时间戳是否有效避免无效的时间戳影响协议逻辑</li>
* <li>支持动态超时时间配置灵活适应不同场景</li>
* <li>提供详细的错误消息包括当前时间和无效时间戳的具体信息</li>
* </ul>
* </p>
*
* <p>
* 使用场景
* <ul>
* <li>协议消息校验确保消息的时间戳在合法范围内避免过期或未来的消息被处理</li>
* <li>安全性校验防止因时间戳问题导致的潜在安全漏洞</li>
* </ul>
* </p>
*
* @see ValidProtocolTimestamp
* @see ConstraintValidator
* @see ProtocolConfigure
* @see ConstValue
* @see MessageUtil
* @see ApiContextUtils
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
public class ValidProtocolTimestampImpl implements ConstraintValidator<ValidProtocolTimestamp, Long> {
/**
* 初始化校验器的方法
* <p>
* 默认实现中调用了父类的初始化逻辑目前无需特殊初始化步骤
* 如果需要从注解中提取参数可在此方法中实现
* </p>
*
* @param constraintAnnotation {@code @ValidProtocolTimestamp} 注解包含相关参数信息
*/
@Override
public void initialize(ValidProtocolTimestamp constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
/**
* 校验时间戳是否合法
* <p>
* 校验逻辑包括
* <ul>
* <li>如果 {@code ProtocolConfigure.CHECK_TIMESTAMP} 配置为 {@code false}则跳过校验直接返回 {@code true}</li>
* <li>如果时间戳晚于当前时间校验失败</li>
* <li>如果时间戳比当前时间早的时间差超过 {@code ProtocolConfigure.TIMEOUT_OF_SECONDS} 配置的超时时间校验失败</li>
* </ul>
* 如果校验失败会通过 {@link ConstraintValidatorContext} 提供详细的错误提示信息
* </p>
*
* @param timeStamp 时间戳将被校验的目标值
* @param constraintValidatorContext 校验器上下文用于构建动态错误消息
* @return {@code true} 表示时间戳合法{@code false} 表示时间戳不合法
*/
@Override
public boolean isValid(Long timeStamp, ConstraintValidatorContext constraintValidatorContext) {
// 如果跳过时间戳校验则直接通过
if (!ProtocolConfigure.CHECK_TIMESTAMP) {
return true;
}
// 格式化时间戳和当前时间
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String date = format.format(timeStamp);
String current = format.format(System.currentTimeMillis());
// 校验时间戳是否晚于当前时间
if (timeStamp > System.currentTimeMillis()) {
String errMsg = MessageUtil.get("timeout.current.format", new String[] {date, current}, ApiContextUtils.getLanguage());
// 禁用默认的错误消息并设置自定义错误提示
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(errMsg).addConstraintViolation();
return false;
}
// 校验时间戳是否超出允许的超时时间
if (System.currentTimeMillis() - timeStamp > ProtocolConfigure.TIMEOUT_OF_SECONDS * ConstValue.MS_OF_SECOND) {
String errMsg = MessageUtil.get("timeout.current.format",
new String[] {date, current, String.valueOf(ProtocolConfigure.TIMEOUT_OF_SECONDS)},
ApiContextUtils.getLanguage());
// 禁用默认的错误消息并设置自定义错误提示
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(errMsg).addConstraintViolation();
return false;
}
// 时间戳合法
return true;
}
}

View File

@ -0,0 +1,9 @@
NORMAL=Normal
LOCKED=Locked
DISABLED=Disabled
DELETED=Deleted
CRYPTO_NONE=Unencrypted
CRYPTO_BASE64=Base64 encoding
CRYPTO_AES128=AES128 encryption
CRYPTO_DES=DES symmetric encryption
CRYPTO_AES256=AES256 encryption

View File

@ -0,0 +1,9 @@
NORMAL=\u6B63\u5E38
LOCKED=\u5DF2\u9501\u5B9A
DISABLED=\u5DF2\u7981\u7528
DELETED=\u5DF2\u5220\u9664
CRYPTO_NONE=\u4E0D\u52A0\u5BC6
CRYPTO_BASE64=Base64\u7F16\u7801
CRYPTO_AES128=AES128\u52A0\u5BC6
CRYPTO_DES=DES\u5BF9\u79F0\u52A0\u5BC6
CRYPTO_AES256=AES256\u52A0\u5BC6

View File

@ -0,0 +1,58 @@
ERR_OK=Successful
ERR_PASSWORD=Incorrect password
ERR_USERNOTFOUND=User does not exist
ERR_PASSWORDMORE=The number of consecutive incorrect passwords reaches the upper limit
ERR_USERLOCK=Password error reaches the upper limit, The user is locked
ERR_PASSWORD_EXPIRED=The password has expired
ERR_ACCOUNT=The user account is abnormal
ERR_USEREXIST=The user already exists
ERR_PASSWORDSIMPLE=The user password strength does not meet the requirement. ERR_INPUTFORMAT=The input information format is incorrect
ERR_INPUTMISS=No necessary input information
ERR_PERMISSION=Operator permission is insufficient
ERR_REQTIMEOUT=Request timeout
ERR_PARAMS=Parameter error
ERR_SYSTEMEXCEPTION=System exception
ERR_UNKNOWNCMD=Unknown command
ERR_LOGOUT=User not logged in
ERR_TOKENTIMEOUT=Token timeout
ERR_TOKENNOTFOUND=Illegal Token
ERR_TOKEN_KEY=Token key error
ERR_MISSAUTHHEAD=Http request missing authentication header
ERR_NOSUCHITEM=No such content
ERR_ITEMEXISTS=The content already exists
ERR_PARAMEXCEPTION=Parameter exception
ERR_DEVICELOCKED=Device locked
ERR_VERSION=Protocol version incompatible, Please upgrade the system
ERR_NOSUCHTYPE=No device of this type exists
ERR_REMOVEMORE=Disable deleting multiple devices at the same time
ERR_TASKRUNNING=Similar tasks are running
ERR_UNSUPPORT=Unsupported operations
ERR_INTERRUPT=Operation interruption
ERR_CALLDEVICE=Failed to call the device
ERR_NOSUCHTASK=No task
ERR_TASKNOTRUNNING=No task is running
ERR_REQUESTTIMEOUT=Request timeout
ERR_UNABLEDISPOSEIP=Unable to handle the IP
ERR_DATABASE=Failed to operate the database
ERR_UNTRUSTHOST=Unauthorized client
ERR_UNTRUSTTOKEN=Unauthorized Token
ERR_UNKNOWNINTERFACE=Interface not provided
ERR_DECRYPT_BASE64=BASE64 decryption failure
ERR_ENCRYPT_BASE64=BASE64 encryption failure
ERR_DECRYPT_AES128=AES128 decryption failure
ERR_ENCRYPT_AES128=AES128 encryption failure
ERR_DECRYPT_3DES=3DES decryption failure
ERR_ENCRYPT_3DES=3DES Encryption failure
ERR_DECRYPT_UNKNOWN=Unsupported decryption algorithm
ERR_ENCRYPT_UNKNOWN=unsupported encryption algorithm
ERR_JSON_ENCODE=Json Sequence number error
ERR_JSON_DECODE=Json deserialization error
ERR_ENCRYPT_AES256=AES256 Encryption failure
ERR_DECRYPT_AES256=AES256 decryption failure
ERR_CRYPTO_KEY=Wrong secret key
ERR_USER_ROLE_NOTEXISTS=The user role does not exist
ERR_RESOURCE_USED=Resource used
err.auth.key.convert=Key algorithm or key conversion error
err.auth.key.verify=Key missing verification data
err.auth.key.timeout=Key expired
err.auth.key.prase=Key resolution error

View File

@ -0,0 +1,59 @@
ERR_OK=\u6210\u529F
ERR_PASSWORD=\u5BC6\u7801\u9519\u8BEF
ERR_USERNOTFOUND=\u7528\u6237\u4E0D\u5B58\u5728
ERR_PASSWORDMORE=\u8FDE\u7EED\u5BC6\u7801\u9519\u8BEF\u8FBE\u4E0A\u9650\uFF0C\u518D\u6B21\u8F93\u5165\u9519\u8BEF\u5C06\u9501\u5B9A\u7528\u6237
ERR_USERLOCK=\u5BC6\u7801\u9519\u8BEF\u8FBE\u4E0A\u9650\uFF0C\u7528\u6237\u88AB\u9501\u5B9A
ERR_PASSWORD_EXPIRED=\u5BC6\u7801\u5DF2\u7ECF\u8FC7\u671F
ERR_ACCOUNT=\u7528\u6237\u8D26\u6237\u5F02\u5E38
ERR_USEREXIST=\u8BE5\u7528\u6237\u5DF2\u7ECF\u5B58\u5728
ERR_PASSWORDSIMPLE=\u7528\u6237\u5BC6\u7801\u5F3A\u5EA6\u4E0D\u7B26\u5408\u8981\u6C42
ERR_INPUTFORMAT=\u8F93\u5165\u4FE1\u606F\u683C\u5F0F\u6709\u8BEF
ERR_INPUTMISS=\u7F3A\u5C11\u5FC5\u8981\u8F93\u5165\u4FE1\u606F
ERR_PERMISSION=\u64CD\u4F5C\u5458\u6743\u9650\u4E0D\u8DB3
ERR_REQTIMEOUT=\u8BF7\u6C42\u8D85\u65F6
ERR_PARAMS=\u53C2\u6570\u9519\u8BEF
ERR_SYSTEMEXCEPTION=\u7CFB\u7EDF\u5F02\u5E38
ERR_UNKNOWNCMD=\u672A\u77E5\u547D\u4EE4
ERR_LOGOUT=\u7528\u6237\u672A\u767B\u5F55
ERR_TOKENTIMEOUT=Token\u8D85\u65F6
ERR_TOKENNOTFOUND=\u975E\u6CD5Token
ERR_TOKEN_KEY=Token \u79D8\u94A5\u9519\u8BEF
ERR_MISSAUTHHEAD=Http \u8BF7\u6C42\u7F3A\u5C11\u8BA4\u8BC1\u5934\u90E8
ERR_NOSUCHITEM=\u6CA1\u6709\u8BE5\u5185\u5BB9
ERR_ITEMEXISTS=\u8BE5\u5185\u5BB9\u5DF2\u7ECF\u5B58\u5728
ERR_PARAMEXCEPTION=\u53C2\u6570\u5F02\u5E38
ERR_DEVICELOCKED=\u8BBE\u5907\u5DF2\u9501\u5B9A
ERR_VERSION=\u534F\u8BAE\u7248\u672C\u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5347\u7EA7\u7CFB\u7EDF
ERR_NOSUCHTYPE=\u6CA1\u6709\u8FD9\u4E2A\u7C7B\u578B\u7684\u8BBE\u5907
ERR_REMOVEMORE=\u7981\u6B62\u540C\u65F6\u5220\u9664\u591A\u4E2A\u8BBE\u5907
ERR_TASKRUNNING=\u540C\u7C7B\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C
ERR_UNSUPPORT=\u4E0D\u652F\u6301\u7684\u64CD\u4F5C
ERR_INTERRUPT=\u64CD\u4F5C\u4E2D\u65AD
ERR_CALLDEVICE=\u8C03\u7528\u8BBE\u5907\u5931\u8D25
ERR_NOSUCHTASK=\u6CA1\u6709\u8BE5\u4EFB\u52A1
ERR_TASKNOTRUNNING=\u8BE5\u4EFB\u52A1\u6CA1\u6709\u8FD0\u884C
ERR_REQUESTTIMEOUT=\u8BF7\u6C42\u8D85\u65F6
ERR_UNABLEDISPOSEIP=\u65E0\u6CD5\u5904\u7F6E\u8BE5IP
ERR_DATABASE=\u64CD\u4F5C\u6570\u636E\u5E93\u5931\u8D25
ERR_UNTRUSTHOST=\u672A\u7ECF\u6388\u6743\u7684\u5BA2\u6237\u7AEF
ERR_UNTRUSTTOKEN=\u672A\u7ECF\u6388\u6743\u7684Token
ERR_UNKNOWNINTERFACE=\u672A\u63D0\u4F9B\u8BE5\u63A5\u53E3
ERR_DECRYPT_BASE64=BASE64\u89E3\u5BC6\u5931\u8D25
ERR_ENCRYPT_BASE64=BASE64\u52A0\u5BC6\u5931\u8D25
ERR_DECRYPT_AES128=AES128\u89E3\u5BC6\u5931\u8D25
ERR_ENCRYPT_AES128=AES128\u52A0\u5BC6\u5931\u8D25
ERR_DECRYPT_3DES=3DES\u89E3\u5BC6\u5931\u8D25
ERR_ENCRYPT_3DES=3DES\u52A0\u5BC6\u5931\u8D25
ERR_DECRYPT_UNKNOWN=\u4E0D\u652F\u6301\u7684\u89E3\u5BC6\u7B97\u6CD5
ERR_ENCRYPT_UNKNOWN=\u4E0D\u652F\u6301\u7684\u52A0\u5BC6\u7B97\u6CD5
ERR_JSON_ENCODE=Json \u5E8F\u5217\u53F7\u9519\u8BEF
ERR_JSON_DECODE=Json \u53CD\u5E8F\u5217\u5316\u9519\u8BEF
ERR_ENCRYPT_AES256=AES256\u52A0\u5BC6\u5931\u8D25
ERR_DECRYPT_AES256=AES256\u89E3\u5BC6\u5931\u8D25
ERR_CRYPTO_KEY=\u9519\u8BEF\u7684\u79D8\u94A5
ERR_USER_ROLE_NOTEXISTS=\u7528\u6237\u89D2\u8272\u4E0D\u5B58\u5728
ERR_RESOURCE_USED=\u8D44\u6E90\u88AB\u5360\u7528
err.auth.key.convert=\u5BC6\u94A5\u7B97\u6CD5\u6216\u8005\u5BC6\u94A5\u8F6C\u6362\u9519\u8BEF
err.auth.key.verify=\u5BC6\u94A5\u7F3A\u5C11\u6821\u9A8C\u6570\u636E
err.auth.key.timeout=\u5BC6\u94A5\u5DF2\u8FC7\u671F
err.auth.key.prase=\u5BC6\u94A5\u89E3\u6790\u9519\u8BEF

View File

@ -0,0 +1,37 @@
SYSLOG_MOD_COMMON=Universal Module
SYSLOG_MOD_SYSLOG=Operation Log Module
SYSLOG_MOD_AUTH=Rights Management Module
SYSLOG_MOD_DICT=Dictionary Module
SYSLOG_MOD_SYSINFO=System Information Module
SYSLOG_MOD_USER_MGR=User Management Module
SYSLOG_MOD_SECURITY=System Safety Module
SYSLOG_TYPE_AUTH=Authentication/Authentication
SYSLOG_TYPE_READ=READ
SYSLOG_TYPE_CREATE=CREATE
SYSLOG_TYPE_DEL=DELETE
SYSLOG_TYPE_MODIFY=MODIFY
SYSLOG_DESC_GET_VERSION=Obtain the current system version information
SYSLOG_DESC_GET_SYSLOG_SUMMARY=Obtain the brief information about operationlogs
SYSLOG_DESC_GET_SYSLOG_DETAIL=Obtain operation logdetails
SYSLOG_DESC_GET_CUR_USR_RES_RIGHT=Get the current user resource rights
SYSLOG_DESC_GET_USR_RES_RIGHT=Obtain user resource rights
SYSLOG_DESC_GET_ALL_USR_GROUP=Get all current user groups
SYSLOG_DESC_CREATE_RESOURCE=Register a new resource
SYSLOG_DESC_DEL_RESOURCE=Delete resource
SYSLOG_DESC_GET_ALL_ENUM_DICT=Get all enumeration dictionary type information of the system
SYSLOG_DESC_GET_ENUM_DETAIL=Gets the details of the system enumeration dictionary
SYSLOG_DESC_CREATE_USER_DICT=Creating a user dictionary
SYSLOG_DESC_MODIFY_USER_DICT=Modify the contents of the user dictionary
SYSLOG_DESC_GET_ALL_USER_DICT=Get all user dictionaries
SYSLOG_DESC_GET_USER_DETAIL=Get the user dictionary content item
SYSLOG_DESC_CREATE_USER_DICT_ITEM=Add user dictionary content items
SYSLOG_DESC_GET_OS_INFO=Obtain information about the current operating system
SYSLOG_DESC_GET_CPU_INFO=Gets current processor information
SYSLOG_DESC_GET_HW_INFO=Obtain system hardware information
SYSLOG_DESC_GET_USER=Get user information
SYSLOG_DESC_GET_USER_LIST=Get user list
SYSLOG_DESC_GET_CUR_USER=get current user information
SYSLOG_DESC_CREATE_USER=Create new user
SYSLOG_DESC_DEL_USER=Delete user
SYSLOG_DESC_LOGOUT=Logout
SYSLOG_DESC_LOGIN=Login

View File

@ -0,0 +1,37 @@
SYSLOG_MOD_COMMON=\u901A\u7528\u6A21\u5757
SYSLOG_MOD_SYSLOG=\u64CD\u4F5C\u65E5\u5FD7\u6A21\u5757
SYSLOG_MOD_AUTH=\u6743\u9650\u7BA1\u7406\u6A21\u5757
SYSLOG_MOD_DICT=\u5B57\u5178\u6A21\u5757
SYSLOG_MOD_SYSINFO=\u7CFB\u7EDF\u4FE1\u606F\u6A21\u5757
SYSLOG_MOD_USER_MGR=\u7528\u6237\u7BA1\u7406\u6A21\u5757
SYSLOG_MOD_SECURITY=\u7CFB\u7EDF\u5B89\u5168\u6A21\u5757
SYSLOG_TYPE_AUTH=\u8BA4\u8BC1/\u9274\u6743
SYSLOG_TYPE_READ=\u8BFB\u53D6
SYSLOG_TYPE_CREATE=\u521B\u5EFA
SYSLOG_TYPE_DEL=\u5220\u9664
SYSLOG_TYPE_MODIFY=\u4FEE\u6539
SYSLOG_DESC_GET_VERSION=\u83B7\u53D6\u5F53\u524D\u7CFB\u7EDF\u7248\u672C\u4FE1\u606F
SYSLOG_DESC_GET_SYSLOG_SUMMARY=\u83B7\u53D6\u64CD\u4F5C\u65E5\u5FD7\u7B80\u8981\u4FE1\u606F
SYSLOG_DESC_GET_SYSLOG_DETAIL=\u83B7\u53D6\u64CD\u4F5C\u65E5\u5FD7\u8BE6\u7EC6\u4FE1\u606F
SYSLOG_DESC_GET_CUR_USR_RES_RIGHT=\u83B7\u53D6\u5F53\u524D\u7528\u6237\u8D44\u6E90\u6743\u9650
SYSLOG_DESC_GET_USR_RES_RIGHT=\u83B7\u53D6\u7528\u6237\u8D44\u6E90\u6743\u9650
SYSLOG_DESC_GET_ALL_USR_GROUP=\u83B7\u53D6\u5F53\u524D\u6240\u6709\u7528\u6237\u7EC4
SYSLOG_DESC_CREATE_RESOURCE=\u6CE8\u518C\u65B0\u7684\u8D44\u6E90
SYSLOG_DESC_DEL_RESOURCE=\u5220\u9664\u8D44\u6E90
SYSLOG_DESC_GET_ALL_ENUM_DICT=\u83B7\u53D6\u7CFB\u7EDF\u6240\u6709\u679A\u4E3E\u5B57\u5178\u7C7B\u578B\u4FE1\u606F
SYSLOG_DESC_GET_ENUM_DETAIL=\u83B7\u53D6\u7CFB\u7EDF\u679A\u4E3E\u5B57\u5178\u8BE6\u7EC6\u5185\u5BB9
SYSLOG_DESC_CREATE_USER_DICT=\u65B0\u5EFA\u7528\u6237\u5B57\u5178
SYSLOG_DESC_MODIFY_USER_DICT=\u4FEE\u6539\u7528\u6237\u5B57\u5178\u5185\u5BB9
SYSLOG_DESC_GET_ALL_USER_DICT=\u83B7\u53D6\u6240\u6709\u7528\u6237\u5B57\u5178
SYSLOG_DESC_GET_USER_DETAIL=\u83B7\u53D6\u7528\u6237\u5B57\u5178\u5185\u5BB9\u9879
SYSLOG_DESC_CREATE_USER_DICT_ITEM=\u65B0\u589E\u7528\u6237\u5B57\u5178\u5185\u5BB9\u9879
SYSLOG_DESC_GET_OS_INFO=\u83B7\u53D6\u5F53\u524D\u64CD\u4F5C\u7CFB\u7EDF\u4FE1\u606F
SYSLOG_DESC_GET_CPU_INFO=\u83B7\u53D6\u5F53\u524D\u5904\u7406\u5668\u4FE1\u606F
SYSLOG_DESC_GET_HW_INFO=\u83B7\u53D6\u7CFB\u7EDF\u786C\u4EF6\u4FE1\u606F
SYSLOG_DESC_GET_USER=\u83B7\u53D6\u7528\u6237\u4FE1\u606F
SYSLOG_DESC_GET_USER_LIST=\u83B7\u53D6\u7528\u6237\u5217\u8868
SYSLOG_DESC_GET_CUR_USER=\u83B7\u53D6\u5F53\u524D\u7528\u6237\u4FE1\u606F
SYSLOG_DESC_CREATE_USER=\u521B\u5EFA\u65B0\u7528\u6237
SYSLOG_DESC_DEL_USER=\u5220\u9664\u7528\u6237
SYSLOG_DESC_LOGOUT=\u6CE8\u9500
SYSLOG_DESC_LOGIN=\u767B\u5F55

View File

@ -0,0 +1,14 @@
item.not_null=field cannot be null
item.not_empty=field cannot be empty string
item.value_range=Field value range [{min}, {max}]
item.value_min=Minimum value is {min}
array.value_range=Array/List size range [{min}, {max}]
array.not_empty=Arrays/List cannot be NULL and elements cannot be empty
timestamp.timeout=The difference between the field value and the current time cannot be greater than the timeout configuration
page.item_size=The value must be an integer multiple of 5
uuid.format=UUID string must match format(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
invalid.character=Invalid character strings exist
password.length=The password size must be SHA256 length
http.request_invalid=Not a legitimate HTTP request method
timeout.current.format=Request time[{0}] greater than current server time[{1}]
timeout.server.format=Request time [{0}] Delay exceeds current server time [{1}] Allowed range {2}(S)

View File

@ -0,0 +1,14 @@
item.not_null=\u5B57\u6BB5\u4E0D\u80FD\u4E3A NULL
item.not_empty=\u5B57\u6BB5\u4E0D\u80FD\u4E3A\u7A7A\u5B57\u7B26\u4E32
item.value_range=\u5B57\u6BB5\u53D6\u503C\u8303\u56F4 [{min}, {max}]
item.value_min=\u6700\u5C0F\u503C\u4E3A {min}
array.value_range=\u6570\u7EC4/\u94FE\u8868\u5143\u7D20\u4E2A\u6570\u53D6\u503C\u8303\u56F4 [{min}, {max}]
array.not_empty=\u6570\u7EC4/\u94FE\u8868\u4E0D\u80FD\u4E3ANULL\u4E14\u5143\u7D20\u4E0D\u80FD\u4E3A\u7A7A
timestamp.timeout=\u5B57\u6BB5\u503C\u4E0E\u5F53\u524D\u65F6\u95F4\u5DEE\u4E0D\u80FD\u5927\u4E8E\u8D85\u65F6\u914D\u7F6E
page.item_size=\u53D6\u503C\u5FC5\u987B\u4E3A 5 \u7684\u6574\u6570\u500D
uuid.format=\u5FC5\u987B\u4E3A\u7B26\u5408UUID\u89C4\u8303(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)\u7684\u5B57\u7B26\u4E32
invalid_character=\u5B57\u7B26\u4E32\u5B58\u5728\u975E\u6CD5\u5B57\u7B26
password.length=\u5BC6\u7801\u957F\u5EA6\u5FC5\u987B\u4E3ASHA256\u7F16\u7801\u540E\u7684\u957F\u5EA6
http.request_invalid=\u4E0D\u662F\u5408\u6CD5\u7684 HTTP \u8BF7\u6C42\u65B9\u6CD5
timeout.current.format=\u8BF7\u6C42\u65F6\u95F4[{0}] \u5927\u4E8E\u5F53\u524D\u670D\u52A1\u5668\u65F6\u95F4[{1}]
timeout.server.format=\u8BF7\u6C42\u65F6\u95F4[{0}] \u5EF6\u65F6\u8D85\u8FC7\u5F53\u524D\u670D\u52A1\u5668\u65F6\u95F4[{1}] \u5141\u8BB8\u8303\u56F4 {2}(S)

View File

@ -0,0 +1,13 @@
package com.cmhi.magent;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MiddlewareAgentApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -0,0 +1,133 @@
package com.cmhi.magent.common;
import com.cmhi.magent.MiddlewareAgentApplication;
import com.cmhi.magent.config.ProtocolConfigure;
import com.cmhi.magent.misc.HelperUtils;
import com.cmhi.magent.misc.ProtocolJsonUtils;
import com.cmhi.magent.pojo.dto.ProtocolReq;
import com.cmhi.magent.pojo.po.BaseRespStatus;
import com.cmhi.magent.pojo.vo.ProtocolResp;
import com.cmhi.magent.service.ProtocolSecurityService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Objects;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {MiddlewareAgentApplication.class})
@AutoConfigureMockMvc
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ActiveProfiles({"common", "test", "user"})
public abstract class TestBaseAuthentication {
protected static final ThreadLocal<HttpHeaders> headersThreadLocal = new ThreadLocal<>();
@Autowired
protected MockMvc mockMvc;
@Resource
private ProtocolSecurityService securityService;
public HttpHeaders getHeaders() {
return headersThreadLocal.get();
}
@BeforeAll
public void setUpClass() {
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
headers.add(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate, br");
headersThreadLocal.set(headers);
}
public <T> Object performanceRestful(RequestMethod reqType, T reqObject, String urlPath, Class<?>[] subRespClass) throws Exception {
MockHttpServletRequestBuilder build = createMockMvcBuilder(urlPath, reqType);
if (Objects.nonNull(reqObject)) {
String sendMsgContent;
ProtocolReq<T> reqInfo = new ProtocolReq<>();
reqInfo.setVer(ConstValue.Protocol.VERSION);
reqInfo.setCryptoType(ProtocolConfigure.SECURITY_PROTOCOL_TYPE);
reqInfo.setTimeStamp(System.currentTimeMillis());
reqInfo.setMsgContent(reqObject);
if (ProtocolConfigure.SECURITY_PROTOCOL_TYPE != ProtoCryptoType.CRYPTO_NONE.getValue()) {
String cipherText = securityService.encryptProtocolString(HelperUtils.getJson(reqObject),
ProtocolConfigure.SECURITY_PROTOCOL_TYPE);
ProtocolReq<String> cipherInfo = new ProtocolReq<>();
cipherInfo.setVer(reqInfo.getVer());
cipherInfo.setCryptoType(reqInfo.getCryptoType());
cipherInfo.setTimeStamp(System.currentTimeMillis());
cipherInfo.setMsgContent(cipherText);
sendMsgContent = HelperUtils.getJson(cipherInfo);
} else {
sendMsgContent = HelperUtils.getJson(reqInfo);
}
build.content(sendMsgContent);
}
String rspValue = mockMvc.perform(build)
.andDo(print())
.andReturn()
.getResponse()
.getContentAsString();
AssertValidString(rspValue);
rspValue = securityService.decryptProtocol(rspValue);
AssertValidString(rspValue);
ProtocolResp<?> resp = ProtocolJsonUtils.jsonGetProtocolResp(rspValue, subRespClass);
Assertions.assertNotNull(resp);
Assertions.assertNotNull(resp.getMsgContent());
Assertions.assertEquals(resp.getCode(), HttpStatus.OK.value());
return resp.getMsgContent();
}
private MockHttpServletRequestBuilder createMockMvcBuilder(String uri, RequestMethod reqType) {
MockHttpServletRequestBuilder build = switch (reqType) {
case PUT -> MockMvcRequestBuilders.put(uri);
case GET -> MockMvcRequestBuilders.get(uri);
case DELETE -> MockMvcRequestBuilders.delete(uri);
default -> MockMvcRequestBuilders.post(uri);
};
build.accept(MediaType.APPLICATION_JSON);
build.headers(getHeaders());
return build;
}
public void AssertValidString(String str) {
Assertions.assertNotNull(str);
Assertions.assertFalse(str.isEmpty());
}
public <T extends BaseRespStatus> void AssertValidCommonResp(T resp) {
Assertions.assertNotNull(resp);
Assertions.assertNotNull(resp.getStatus());
Assertions.assertNotNull(resp.getMessage());
Assertions.assertNotEquals(0, resp.getMessage().length);
Assertions.assertEquals(resp.getStatus(), ErrorCode.ERR_OK.getCode());
}
}

View File

@ -0,0 +1,44 @@
package com.cmhi.magent.controller;
import com.cmhi.magent.common.TestBaseAuthentication;
import com.cmhi.magent.pojo.vo.VersionResp;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.bind.annotation.RequestMethod;
@ExtendWith({SpringExtension.class})
@SuppressWarnings("java:S5786")
public class CommonFrameworkApiTest extends TestBaseAuthentication {
@Test
@DisplayName("获取版本信息")
void testGetVersion() throws Exception {
VersionResp resp = (VersionResp) performanceRestful(RequestMethod.GET, null, "/api/version", new Class[] {VersionResp.class});
AssertValidCommonResp(resp);
Assertions.assertNotNull(resp.getVersion());
Assertions.assertNotNull(resp.getVersion().getTagName());
AssertValidString(resp.getVersion().getCommitId());
AssertValidString(resp.getVersion().getCommitDescribe());
AssertValidString(resp.getVersion().getCommitTime());
AssertValidString(resp.getVersion().getBuildTime());
AssertValidString(resp.getVersion().getGitBranch());
}
@Test
@DisplayName("获取版本信息(POST)")
void testGetVersionV2() throws Exception {
VersionResp resp = (VersionResp) performanceRestful(RequestMethod.POST, null, "/api/version", new Class[] {VersionResp.class});
AssertValidCommonResp(resp);
Assertions.assertNotNull(resp.getVersion());
Assertions.assertNotNull(resp.getVersion().getTagName());
AssertValidString(resp.getVersion().getCommitId());
AssertValidString(resp.getVersion().getCommitDescribe());
AssertValidString(resp.getVersion().getCommitTime());
AssertValidString(resp.getVersion().getBuildTime());
AssertValidString(resp.getVersion().getGitBranch());
}
}

View File

@ -0,0 +1,56 @@
package com.cmhi.magent.service.impl;
import com.cmhi.magent.pojo.po.HwInfo;
import com.cmhi.magent.pojo.po.ProcessorInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class SystemInfoServiceImplTest {
private SystemInfoServiceImpl systemInfoServiceImplUnderTest;
@BeforeEach
void setUp() {
systemInfoServiceImplUnderTest = new SystemInfoServiceImpl();
}
@Test
void testGetOsName() {
// Setup
// Run the test
final String result = systemInfoServiceImplUnderTest.getOsName();
// Verify the results
assertThat(result).isNotEmpty();
}
@Test
void testGetOsBootTimeStamp() {
// Setup
// Run the test
final long result = systemInfoServiceImplUnderTest.getOsBootTimeStamp();
// Verify the results
assertThat(result).isPositive();
}
@Test
void testGetProcessInfo() {
// Run the test
final ProcessorInfo result = systemInfoServiceImplUnderTest.getProcessInfo();
// Verify the results
assertThat(result.getCpuIdentifier()).isNotEmpty();
}
@Test
void testGetHardwareInfo() {
// Run the test
final HwInfo result = systemInfoServiceImplUnderTest.getHardwareInfo();
// Verify the results
assertThat(result.getModel()).isNotEmpty();
}
}