commit 3aa180bbd21b7c0979af542492ff99f77e12207c Author: lihansani Date: Mon Jul 13 13:25:21 2026 +0800 first commit diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..9541d5f --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(ls \"e:\\\\projects\\\\wxwx\\\\projects_job\\\\UniversalTestSoftware\\\\UDP_Projects\\\\Code\\\\Client\\\\Back_end\\\\\")", + "Read(//e/projects/wxwx/projects_job/UniversalTestSoftware/UDP_Projects/Code/Client/**)" + ] + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..12fbe1e --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# 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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/doc/协议描述.pdf b/doc/协议描述.pdf new file mode 100644 index 0000000..f46b6a9 Binary files /dev/null and b/doc/协议描述.pdf differ diff --git a/doc/通用闭环验证系统应用软件研制技术要求-20250918.docx b/doc/通用闭环验证系统应用软件研制技术要求-20250918.docx new file mode 100644 index 0000000..f9f5d96 Binary files /dev/null and b/doc/通用闭环验证系统应用软件研制技术要求-20250918.docx differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/mvnw @@ -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-,maven-mvnd--}/ +[ -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 "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/mvnw.cmd @@ -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-,maven-mvnd--}/ +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" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1b0fb0f --- /dev/null +++ b/pom.xml @@ -0,0 +1,275 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.5 + + + com.example + universaltestsoftware_client_api + 0.0.1-SNAPSHOT + universaltestsoftware_client_api + universaltestsoftware_client_api + + + + + + + + + + + + + + + 1.8 + 1.8 + 1.8 + + + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.17.1 + + + + org.flywaydb + flyway-core + 5.2.1 + + + + com.opencsv + opencsv + 5.9 + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.17.1 + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-properties + 2.17.1 + + + + commons-io + commons-io + 2.5 + + + com.alibaba + druid-spring-boot-starter + 1.2.16 + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + commons-io + commons-io + 2.14.0 + + + + com.alibaba + easyexcel + 4.0.3 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + io.netty + netty-all + 4.1.58.Final + + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-test + test + + + mysql + mysql-connector-java + 5.1.49 + runtime + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + com.baomidou + mybatis-plus-generator + 3.5.3.1 + + + org.freemarker + freemarker + 2.3.32 + + + io.springfox + springfox-boot-starter + 3.0.0 + + + io.springfox + springfox-swagger2 + 3.0.0 + + + + io.springfox + springfox-swagger-ui + 3.0.0 + + + + com.alibaba + fastjson + 1.2.83 + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpmime + + + + + cn.hutool + hutool-all + 5.8.20 + + + + com.sun.xml.bind + jaxb-core + 2.3.0 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + com.sun.xml.bind + jaxb-impl + 2.3.1 + + + com.sun.istack + istack-commons-runtime + 3.0.11 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + io.reactivex.rxjava3 + rxjava + 3.1.6 + + + + org.springframework.boot + spring-boot-loader + provided + + + + org.yeauty + netty-websocket-spring-boot-starter + 0.12.0 + + + + + + + + + + org.springframework.cloud + spring-cloud-dependencies + 2020.0.3 + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + + + + + + diff --git a/src/main/java/com/cbsd/client/Test.java b/src/main/java/com/cbsd/client/Test.java new file mode 100644 index 0000000..10cc859 --- /dev/null +++ b/src/main/java/com/cbsd/client/Test.java @@ -0,0 +1,6 @@ +package com.cbsd.client; + +public interface Test { + public String test(); + +} diff --git a/src/main/java/com/cbsd/client/branch/IBranchConditionInterface.java b/src/main/java/com/cbsd/client/branch/IBranchConditionInterface.java new file mode 100644 index 0000000..055353f --- /dev/null +++ b/src/main/java/com/cbsd/client/branch/IBranchConditionInterface.java @@ -0,0 +1,29 @@ +package com.cbsd.client.branch; + +import java.util.Map; + +/** + * 分支条件脚本 + * 根据通道绑定的ICD的协议中的字段,对数据进行判断返回需要跳转的指令名称 + */ +public interface IBranchConditionInterface { + + /** + * 分支条件 + * @param originalCommand 原始指令数据 + * @param preTreatmentCommand 预处理指令数据,若无预处理,则为原始指令数据 + * @param repeatCount 重复次数 + * @param hitCount 命中次数 + * @param latestHitTime 最后命中时间 + * @param dataMap 数据集合,key为协议字段属性名(label),value为物理值 + * @return 需要跳转的指令名称,为空则为不跳转 + */ + String branchCondition(String originalCommand, String preTreatmentCommand, int repeatCount, int hitCount, String latestHitTime, Map dataMap); + + /** + * 描述 + * + * @return 描述内容 + */ + String comment(); +} diff --git a/src/main/java/com/cbsd/client/check/ICheckInterface.java b/src/main/java/com/cbsd/client/check/ICheckInterface.java new file mode 100644 index 0000000..68cd068 --- /dev/null +++ b/src/main/java/com/cbsd/client/check/ICheckInterface.java @@ -0,0 +1,30 @@ +package com.cbsd.client.check; + +import java.util.List; +import java.util.Map; + +/** + * 检查脚本接口 + * 根据通道绑定的ICD的协议中的字段,对数据进行检查并写入日志 + */ +public interface ICheckInterface { + + /** + * 检查 + * @param originalCommand 原始指令数据 + * @param preTreatmentCommand 预处理指令数据,若无预处理,则为原始指令数据 + * @param repeatCount 重复次数 + * @param hitCount 命中次数 + * @param latestHitTime 最后命中时间 + * @param dataMap 数据集合,key为协议字段属性名(label),value为物理值 + * @return 日志信息列表 + */ + List check(String originalCommand, String preTreatmentCommand, int repeatCount, int hitCount, String latestHitTime, Map dataMap); + + /** + * 描述 + * + * @return 描述内容 + */ + String comment(); +} diff --git a/src/main/java/com/cbsd/client/parse/IParseDataInterface.java b/src/main/java/com/cbsd/client/parse/IParseDataInterface.java new file mode 100644 index 0000000..c3f08cb --- /dev/null +++ b/src/main/java/com/cbsd/client/parse/IParseDataInterface.java @@ -0,0 +1,23 @@ +package com.cbsd.client.parse; + +public interface IParseDataInterface { + + /** + * 从数据解析物理值 + * + * @param sn 当前字段序号 + * @param sourceData 源数据 + * @param fieldData 当前字段数据 + * @param endian BIG:大端序 LITTLE:小端序 + * @param binStr 二进制字符串 + * @return 解析后的数据值 + */ + String parsePhysicalFromData(int sn, byte[] sourceData, byte[] fieldData, String endian, String binStr); + + /** + * 当前解析描述 + * + * @return 描述内容 + */ + String comment(); +} diff --git a/src/main/java/com/cbsd/client/pretreatment/CalculateUtils.java b/src/main/java/com/cbsd/client/pretreatment/CalculateUtils.java new file mode 100644 index 0000000..8dac80e --- /dev/null +++ b/src/main/java/com/cbsd/client/pretreatment/CalculateUtils.java @@ -0,0 +1,4 @@ +package com.cbsd.client.pretreatment; + +public class CalculateUtils { +} diff --git a/src/main/java/com/cbsd/client/pretreatment/IPreTreatmentInterface.java b/src/main/java/com/cbsd/client/pretreatment/IPreTreatmentInterface.java new file mode 100644 index 0000000..d9a74f5 --- /dev/null +++ b/src/main/java/com/cbsd/client/pretreatment/IPreTreatmentInterface.java @@ -0,0 +1,30 @@ +package com.cbsd.client.pretreatment; + +import java.util.Map; + +/** + * 预处理脚本接口 + * 指令数据、命中次数、命中时间、重复次数、 校验(内置方法)? + */ +public interface IPreTreatmentInterface { + + /** + * 预处理 + * + * @param command 指令内容 + * @param repeatCount 重复次数 + * @param hitCount 命中次数 + * @param latestHitTime 最后命中时间 + * @param dataMap 数据集合,key为协议字段属性名(label),value为物理值 + * @param calculateUtils 计算工具类 + * @return 结果 + */ + String pretreatment(String command, int repeatCount, int hitCount, String latestHitTime, Map dataMap, CalculateUtils calculateUtils); + + /** + * 描述 + * + * @return 描述内容 + */ + String comment(); +} diff --git a/src/main/java/com/cbsd/client/verification/IVerificationInterface.java b/src/main/java/com/cbsd/client/verification/IVerificationInterface.java new file mode 100644 index 0000000..bb8d401 --- /dev/null +++ b/src/main/java/com/cbsd/client/verification/IVerificationInterface.java @@ -0,0 +1,20 @@ +package com.cbsd.client.verification; + +public interface IVerificationInterface { + /** + * 校验 + * + * @param data 校验数据 + * @param verify 校验位 + * @param verifyUtils 校验工具类 + * @return 校验结果 + */ + boolean verification(byte[] data, byte[] verify, VerifyUtils verifyUtils); + + /** + * 当前解析描述 + * + * @return 描述内容 + */ + String comment(); +} diff --git a/src/main/java/com/cbsd/client/verification/VerifyUtils.java b/src/main/java/com/cbsd/client/verification/VerifyUtils.java new file mode 100644 index 0000000..c76e7d9 --- /dev/null +++ b/src/main/java/com/cbsd/client/verification/VerifyUtils.java @@ -0,0 +1,55 @@ +package com.cbsd.client.verification; + +public class VerifyUtils { + + + /** + * 验证数据的CRC16校验值 + * + * @param data 包含CRC校验值的数据(CRC在数据末尾,2字节) + * @return 校验结果,true表示校验通过,false表示校验失败 + */ + public boolean verifyCRC(byte[] data) { + if (data == null || data.length < 2) { + return false; + } + + // 提取数据部分(不包含CRC) + byte[] dataWithoutCRC = new byte[data.length - 2]; + System.arraycopy(data, 0, dataWithoutCRC, 0, data.length - 2); + + // 计算CRC + byte[] calculatedCRC = calculateCRC(dataWithoutCRC); + + // 比较计算得到的CRC与数据中包含的CRC + return calculatedCRC[0] == data[data.length - 2] && + calculatedCRC[1] == data[data.length - 1]; + } + + /** + * 计算Modbus CRC16校验值 + * + * @param data 待校验的数据 + * @return CRC16校验值,2字节数组 + */ + private byte[] calculateCRC(byte[] data) { + int crc = 0xFFFF; + for (byte b : data) { + crc ^= (b & 0xFF); + for (int i = 0; i < 8; i++) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + // 将CRC转换为字节数组(低字节在前,高字节在后) + byte[] result = new byte[2]; + result[0] = (byte) (crc & 0xFF); + result[1] = (byte) ((crc >> 8) & 0xFF); + + return result; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/DynamicClassBootstrap.java b/src/main/java/com/cbsd/universaltestsoftware_client/DynamicClassBootstrap.java new file mode 100644 index 0000000..8f631b1 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/DynamicClassBootstrap.java @@ -0,0 +1,59 @@ +package com.cbsd.universaltestsoftware_client; + +import com.cbsd.universaltestsoftware_client.util.DynamicClassLoader; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class DynamicClassBootstrap { + + @Value("${cbsd.uploadfilesrootpath}") + private String rootPath; + + // 最终缓存:全限定类名 → Class,运行时直接拿 + public static final Map> CLAZZ_CACHE = new ConcurrentHashMap<>(); + +// @PostConstruct // Spring 容器初始化完成后立即执行 + public void loadAllCompiledClasses() throws Exception { + Path root = Paths.get(rootPath); + if (!Files.exists(root)) { + return; + } + + // 自定义类加载器 + DynamicClassLoader loader = new DynamicClassLoader(rootPath); + + // 遍历目录树 + Files.walk(root) + .filter(p -> p.toString().endsWith(".class")) + .forEach(p -> { + // 把文件路径 → 全限定类名 + String fullName = pathToFqn(root, p); + try { + Class clazz = loader.loadClass(fullName); + CLAZZ_CACHE.put(fullName, clazz); + } catch (ClassNotFoundException e) { + // 记录日志即可 + e.printStackTrace(); + } + }); + + System.out.println("已预加载 class 数量:" + CLAZZ_CACHE.size()); + } + + // 工具:把文件路径转换成 com.xxx.yyy.ClassName + private String pathToFqn(Path root, Path classFile) { + String relative = root.relativize(classFile).toString(); + return relative + .replace(File.separatorChar, '.') + .replace(".class", ""); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/UniversaltestsoftwareClientApiApplication.java b/src/main/java/com/cbsd/universaltestsoftware_client/UniversaltestsoftwareClientApiApplication.java new file mode 100644 index 0000000..da689e3 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/UniversaltestsoftwareClientApiApplication.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@MapperScan("com.cbsd.*.mapper") +@ComponentScan("com.cbsd.*") +@EnableAsync +@EnableScheduling // 开启定时任务功能 +public class UniversaltestsoftwareClientApiApplication { + + public static void main(String[] args) { + SpringApplication.run(UniversaltestsoftwareClientApiApplication.class, args); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/boardTask/PushHolder.java b/src/main/java/com/cbsd/universaltestsoftware_client/boardTask/PushHolder.java new file mode 100644 index 0000000..34210b7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/boardTask/PushHolder.java @@ -0,0 +1,173 @@ +package com.cbsd.universaltestsoftware_client.boardTask; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.cbsd.universaltestsoftware_client.dto.PageChannelDataDto; +import com.cbsd.universaltestsoftware_client.entity.ChannelData; +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataMapper; +import com.cbsd.universaltestsoftware_client.mapper.DataBoardChannelMapper; +import com.cbsd.universaltestsoftware_client.parser.model.FiledParseData; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.vo.ChannelDataVo; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Component +public class PushHolder { + // 使用ConcurrentHashMap存储每个dataBoardId对应的PushSession,确保线程安全 + private final ConcurrentHashMap POOL = new ConcurrentHashMap<>(); + + @Resource + private DataBoardChannelMapper dataBoardChannelMapper; + + @Resource + private ChannelDataMapper channelDataMapper; + + // 根据dataBoardId获取对应的PushSession + public PushSession get(String id) { + return POOL.get(id); + } + + // 创建一个新的PushSession + public synchronized PushSession create(PageChannelDataDto cmd) { + // 如果已经存在相同的dataBoardId,先停止旧的PushSession + stop(cmd.getDataBoardId()); + PushSession s = new PushSession(); + s.boardId = cmd.getDataBoardId(); + s.pageSize = cmd.getPageSize(); + s.startTime = cmd.getStartTime(); + s.endTime = cmd.getEndTime(); + int want = cmd.getPageSize() * 6; // 初始拉取数据条数为pageSize的4倍 + cmd.setWant(want); + List first = getData(cmd); // 获取第一批数据 + s.queue.addAll(first.subList(0, Math.min(first.size(), want))); // 将第一批数据加入队列 + if (!first.isEmpty()) { + int idx = Math.min(first.size(), want) - 1; + if (idx >= 0) { + long timestamp = first.get(idx).getTimestamp(); + s.lastTime = DateUtils.timestampToDateString(timestamp, "yyyy-MM-dd HH:mm:ss.SSS"); // 更新最后时间 + } + } + s.worker = new Thread(() -> pullLoop(s), "pull-" + s.boardId); // 创建拉取数据的线程 + s.worker.setDaemon(true); + s.worker.start(); + POOL.put(s.boardId, s); // 将新的PushSession加入POOL + return s; + } + + // 停止指定dataBoardId的PushSession + public synchronized void stop(String boardId) { + PushSession s = POOL.remove(boardId); + if (s != null) s.shutdown(); // 调用shutdown方法停止线程 + } + + // 更新指定dataBoardId的分页参数 + public synchronized void update(PageChannelDataDto cmd) { + PushSession s = POOL.get(cmd.getDataBoardId()); + if (s == null) { + create(cmd); // 如果不存在,创建一个新的PushSession + return; + } + // 如果startTime或endTime发生变化,停止旧的PushSession并创建新的 + if (!cmd.getStartTime().equals(s.startTime) || !cmd.getEndTime().equals(s.endTime)) { + s.shutdown(); + create(cmd); + } else { + // 如果只有pageSize发生变化,直接更新pageSize + s.pageSize = cmd.getPageSize(); + } + } + + // 拉取数据的循环逻辑 + private void pullLoop(PushSession s) { + while (s.isRunning()) { + try { + int want = s.pageSize * 5; // 每次拉取数据条数为pageSize的3倍 + if (s.queue.size() < want) { + PageChannelDataDto cmd = new PageChannelDataDto(); + cmd.setWant(want); + cmd.setDataBoardId(s.boardId); + cmd.setStartTime(s.startTime); + cmd.setEndTime(s.endTime); + cmd.setLastTime(s.lastTime); + List chunk = getData(cmd); // 获取一批数据 + if (chunk.isEmpty()) { + break; // 如果没有数据,退出循环 + } + int gap = want - s.queue.size(); + s.queue.addAll(chunk.subList(0, Math.min(chunk.size(), gap))); // 将数据加入队列 + long timestamp = chunk.get(Math.min(chunk.size(), gap) - 1).getTimestamp(); + s.lastTime = DateUtils.timestampToDateString(timestamp, "yyyy-MM-dd HH:mm:ss.SSS"); // 更新最后时间 + } + TimeUnit.SECONDS.sleep(4); // 每5秒拉取一次数据 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + // 根据PageChannelDataDto获取数据 + public List getData(PageChannelDataDto pageChannelDataDto) { + ArrayList channelDataVos = new ArrayList<>(); + try { + LambdaQueryWrapper dataBoardChannelLambdaQueryWrapper = new LambdaQueryWrapper<>(); + dataBoardChannelLambdaQueryWrapper.eq(DataBoardChannel::getDataBoardId, pageChannelDataDto.getDataBoardId()) + .groupBy(DataBoardChannel::getChannelId); + List dataBoardChannelList = dataBoardChannelMapper.selectList(dataBoardChannelLambdaQueryWrapper); + List channelIds = dataBoardChannelList.stream().map(DataBoardChannel::getChannelId).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(channelIds)) { + LambdaQueryWrapper channelDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + channelDataLambdaQueryWrapper + .between(StringUtils.isNotBlank(pageChannelDataDto.getStartTime()) && StringUtils.isNotBlank(pageChannelDataDto.getEndTime()), ChannelData::getTime, pageChannelDataDto.getStartTime(), pageChannelDataDto.getEndTime()) + .in(ChannelData::getChannelId, channelIds) + .gt(StringUtils.isNotBlank(pageChannelDataDto.getLastTime()), ChannelData::getTime, pageChannelDataDto.getLastTime()) + .last("limit " + pageChannelDataDto.getWant()) + .orderByAsc(ChannelData::getTime); + List channelDataList = channelDataMapper.selectList(channelDataLambdaQueryWrapper); + + for (ChannelData channelData : channelDataList) { + ArrayList filedParseData = new ArrayList<>(); + ChannelDataVo parserMessage = new ChannelDataVo(); + parserMessage.setChannelId(channelData.getChannelId()); + + if (StringUtils.isNotBlank(channelData.getData())) { + try { + List filedParseDataList = JSON.parseArray(channelData.getData(), FiledParseData.class); + filedParseData.addAll(filedParseDataList); + } catch (Exception e) { + e.printStackTrace(); + } + } + parserMessage.setFiledParseDataList(filedParseData); + parserMessage.setTimestamp(convertToTimestamp(channelData.getTime())); + parserMessage.setDataBoardId(pageChannelDataDto.getDataBoardId()); + channelDataVos.add(parserMessage); + } + } + }catch (Exception e){ + e.printStackTrace(); + + } + return channelDataVos; + } + + // 将时间字符串转换为时间戳 + public static long convertToTimestamp(String dateTimeStr) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, formatter); + return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/boardTask/PushSession.java b/src/main/java/com/cbsd/universaltestsoftware_client/boardTask/PushSession.java new file mode 100644 index 0000000..1c9147b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/boardTask/PushSession.java @@ -0,0 +1,40 @@ +package com.cbsd.universaltestsoftware_client.boardTask; + +import com.cbsd.universaltestsoftware_client.vo.ChannelDataVo; +import lombok.Data; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +@Data +public class PushSession { + // 用于控制线程是否运行的原子布尔变量 + private final AtomicBoolean run = new AtomicBoolean(true); + // 每次拉取的数据条数 + volatile int pageSize; + // 上次拉取的最后时间(字符串格式) + volatile String lastTime; + // 数据拉取的开始时间 + volatile String startTime; + // 数据拉取的结束时间 + volatile String endTime; + // 存储数据的阻塞队列 + final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + // 数据拉取的线程 + Thread worker; + // 数据板ID + String boardId; + + // 停止线程的方法 + public void shutdown() { + run.set(false); // 设置线程停止标志 + if (worker != null) { + worker.interrupt(); // 中断线程 + } + } + + // 检查线程是否正在运行 + public boolean isRunning() { + return run.get(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/BasePage.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/BasePage.java new file mode 100644 index 0000000..198f664 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/BasePage.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.config; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class BasePage extends Page { + + //关键字查询 + @ApiModelProperty("关键字查询") + private String keyword; + + @ApiModelProperty(name = "开始时间") + private String startTime; + + @ApiModelProperty(name = "结束时间") + private String endTime; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/ConnectionFactory.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/ConnectionFactory.java new file mode 100644 index 0000000..dfde542 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/ConnectionFactory.java @@ -0,0 +1,26 @@ +package com.cbsd.universaltestsoftware_client.config; + +import org.apache.logging.log4j.core.appender.db.jdbc.AbstractConnectionSource; +import org.springframework.util.Assert; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class ConnectionFactory extends AbstractConnectionSource +{ + private final DataSource dataSource; + + public ConnectionFactory(DataSource dataSource) + { + Assert.notNull(dataSource, "dataSource can not be null"); + this.dataSource = dataSource; + } + + @Override + public Connection getConnection() + throws SQLException + { + return dataSource.getConnection(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/GeneratorCode.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/GeneratorCode.java new file mode 100644 index 0000000..8df22fd --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/GeneratorCode.java @@ -0,0 +1,82 @@ +package com.cbsd.universaltestsoftware_client.config; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.config.DataSourceConfig; +import com.baomidou.mybatisplus.generator.config.OutputFile; +import com.baomidou.mybatisplus.generator.config.rules.DateType; +import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; + +import java.sql.Types; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @ClassName GeneratorCode + * @Author FY + * @Version 1.0.0 + * @Description 代码生成器 + * @CreateTime 2023年04月22日 15:49:00 + */ +public class + +GeneratorCode { + /** + * 数据源配置 + */ + private static final DataSourceConfig.Builder DATA_SOURCE_CONFIG = new DataSourceConfig + .Builder("jdbc:mysql://192.168.100.40:3306/universaltestsoftware_client_api?serverTimezone=GMT%2B8&characterEncoding=utf8&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8", "root", "Fushoukeji1234"); + + + /** + * 代码生成器 + * + * @param args + */ + public static void main(String[] args) { + FastAutoGenerator.create(DATA_SOURCE_CONFIG) + .globalConfig(builder -> { + builder.author("admin") // 设置作者 + .enableSwagger() // 开启 swagger 模式 + .disableOpenDir() + .dateType(DateType.ONLY_DATE) + .outputDir("C:\\gs\\project\\universaltestsoftware_client_api\\src\\main\\java"); // 指定输出目录 + }) + .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { + int typeCode = metaInfo.getJdbcType().TYPE_CODE; + if (typeCode == Types.SMALLINT) { + // 自定义类型转换 + return DbColumnType.INTEGER; + } + return typeRegistry.getColumnType(metaInfo); + + })) + .packageConfig(builder -> { + builder.entity("entity").parent("com.cbsd.universaltestsoftware_client") // 设置父包名 + .pathInfo(Collections.singletonMap(OutputFile.xml, "C:\\gs\\project\\universaltestsoftware_client_api\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径 + }) + .strategyConfig(builder -> { + builder.addInclude(getTables("data_board,data_board_channel")) // 设置需要生成的表名 + .addTablePrefix(""); // 设置过滤表前缀 + builder.serviceBuilder().formatServiceFileName("%sService"); + builder.entityBuilder().logicDeleteColumnName("del").enableFileOverride().idType(IdType.ASSIGN_ID).enableTableFieldAnnotation().enableLombok().disableSerialVersionUID(); + builder.mapperBuilder().enableFileOverride().enableBaseResultMap().enableBaseColumnList(); + builder.controllerBuilder().enableRestStyle(); + }) + .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板 + .execute(); + + } + + /** + * 处理多表情况 + * + * @param tables 表名 + * @return List + */ + protected static List getTables(String tables) { + return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/IBaseMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/IBaseMapper.java new file mode 100644 index 0000000..f886263 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/IBaseMapper.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.config; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface IBaseMapper extends BaseMapper { + + /** + * 批量插入 + * @param batchList 批量数据 + * @return insert 数量 + */ + int insertBatchSomeColumn(@Param("list") List batchList); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/InsertBatchSqlInjector.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/InsertBatchSqlInjector.java new file mode 100644 index 0000000..d28b01e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/InsertBatchSqlInjector.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.config; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn; +import org.apache.ibatis.session.Configuration; + +import java.util.List; + +/** + * 批量插入 SQL 注入器 + */ +public class InsertBatchSqlInjector extends DefaultSqlInjector { + + @Override + public List getMethodList(Class mapperClass, TableInfo tableInfo) { + List methodList = super.getMethodList(mapperClass, tableInfo); + methodList.add(new InsertBatchSomeColumn()); + return methodList; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/Log4j2Configuration.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/Log4j2Configuration.java new file mode 100644 index 0000000..de69858 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/Log4j2Configuration.java @@ -0,0 +1,62 @@ +package com.cbsd.universaltestsoftware_client.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.db.ColumnMapping; +import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig; +import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; + +@Component +public class Log4j2Configuration implements ApplicationListener +{ + private final DataSource dataSource; + + public Log4j2Configuration(DataSource dataSource) + { + this.dataSource = dataSource; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) + { + final LoggerContext ctx = LoggerContext.getContext(false); + final ColumnConfig[] cc = + {ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("event_id").setPattern("%X{id}").setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("event_date").setEventTimestamp(true).setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("thread").setPattern("%t %x").setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("log_class").setPattern("%C").setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("method").setPattern("%M").setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("message").setPattern("%m").setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("exception").setPattern("%ex{full}").setUnicode(false).build(), + ColumnConfig.newBuilder().setConfiguration(ctx.getConfiguration()).setName("level").setPattern("%level").setUnicode(false).build(), + ColumnConfig.newBuilder() + .setConfiguration(ctx.getConfiguration()) + .setName("time") + .setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS}") + .setUnicode(false) + .build()}; + + // 配置appender + final Appender appender = JdbcAppender.newBuilder() + .setName("databaseAppender") + .setIgnoreExceptions(false) + .setConnectionSource(new ConnectionFactory(dataSource)) + .setTableName("boot_log") + .setColumnConfigs(cc) + .setColumnMappings(new ColumnMapping[0]) + .build(); + appender.start(); + + ctx.getConfiguration().addAppender(appender); + + // 指定哪些logger输出的日志保存在mysql中 + ctx.getConfiguration().getLoggerConfig("com.cbsd").addAppender(appender, Level.INFO, null); + ctx.updateLoggers(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/LogPoolManager.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/LogPoolManager.java new file mode 100644 index 0000000..758c7ca --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/LogPoolManager.java @@ -0,0 +1,65 @@ +package com.cbsd.universaltestsoftware_client.config; + +import com.alibaba.druid.pool.DruidDataSource; +import com.baomidou.mybatisplus.core.toolkit.Assert; + +import com.cbsd.universaltestsoftware_client.util.YamlUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; + +import javax.sql.DataSource; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +public final class LogPoolManager { + private static DataSource dataSource; + private static String configLocation; + + private LogPoolManager() { + super(); + } + + public static void setConfigLocation(String configLocation) { + LogPoolManager.configLocation = configLocation; + } + + public static synchronized void init() { + if (dataSource != null) { + return; // 避免重复初始化 + } + + try { + if (StringUtils.isNotBlank(configLocation)) { + File file = new File(configLocation); + String text = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + Properties props = YamlUtils.yamlToProperties(text); + + // 直接实例化DruidDataSource,而非通过DataSourceBuilder + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setUrl(props.getProperty("spring.datasource.url")); + druidDataSource.setUsername(props.getProperty("spring.datasource.username")); + druidDataSource.setPassword(props.getProperty("spring.datasource.password")); + + // 关键:禁用SQL解析功能(解决ResultSetColumnMetaData错误) + druidDataSource.setPoolPreparedStatements(false); + druidDataSource.setFilters("stat"); // 只保留统计功能,移除wall等可能引起解析的过滤器 + druidDataSource.setProxyFilters(null); // 清空代理过滤器 + + dataSource = druidDataSource; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Connection getConnection() throws SQLException { + if (dataSource == null) { + init(); + } + Assert.notNull(dataSource, "dataSource can not be null"); + return dataSource.getConnection(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/MyBatisPlusConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..510e747 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/MyBatisPlusConfig.java @@ -0,0 +1,41 @@ +package com.cbsd.universaltestsoftware_client.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.StringValue; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MyBatisPlusConfig { + + /** + * 分页插件配置 + * + * @return + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + //乐观锁插件 + interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); + + return interceptor; + } + + /** + * 自定义批量插入 SQL 注入器 + */ + @Bean + public InsertBatchSqlInjector insertBatchSqlInjector() { + return new InsertBatchSqlInjector(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/config/SwaggerConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/config/SwaggerConfig.java new file mode 100644 index 0000000..f287391 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/config/SwaggerConfig.java @@ -0,0 +1,82 @@ +package com.cbsd.universaltestsoftware_client.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.oas.annotations.EnableOpenApi; +import springfox.documentation.service.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Configuration //表明当前类是配置类 +@EnableOpenApi //表示开启生成接口文档功能(只有开启了OpenApi,才可以实现生成接口文档的功能) +public class SwaggerConfig { + + + @Bean + public Docket docket() { + return new Docket(DocumentationType.OAS_30) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("com.cbsd.universaltestsoftware_client.controller")) + .paths(PathSelectors.any()) + .build() + .securitySchemes(securitySchemes()) // 添加安全方案 + .securityContexts(Collections.singletonList(securityContext())); // 添加上下文 + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("Swagger3 API文档") + .description("RESTFul 风格接口") + .version("1.0") + .build(); + } + + private List securitySchemes() { + List apiKeyList = new ArrayList<>(); + // 添加sessionId认证 + apiKeyList.add(new ApiKey("sessionId", "sessionId", "header")); + // 添加tenantId请求头支持 + // apiKeyList.add(new ApiKey("tenantId", "tenantId", "header")); + return apiKeyList; + } + + /** + * 设置swagger认证的安全上下文 + */ + private SecurityContext securityContext() { + return SecurityContext.builder() + .securityReferences(defaultAuth()) + .build(); + } + + /** + * 默认的安全引用,包含sessionId和tenantId + */ + private List defaultAuth() { + AuthorizationScope[] authorizationScopes = new AuthorizationScope[]{ + new AuthorizationScope("web", "All scope is trusted!") + }; + + return Arrays.asList( + new SecurityReference("sessionId", authorizationScopes) + // new SecurityReference("tenantId", authorizationScopes) + ); + } + + + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/BackageFileController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/BackageFileController.java new file mode 100644 index 0000000..46d0df6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/BackageFileController.java @@ -0,0 +1,339 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.cbsd.universaltestsoftware_client.dto.FileInfo; +import com.cbsd.universaltestsoftware_client.util.*; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.math.BigInteger; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Api(tags = "文件上传下载相关操作的接口文档") +@RestController() +@RequestMapping("/api/common/backstage/file") +public class BackageFileController { + // 得到一个用来记录日志的对象,这样在打印信息时能够标记打印的是哪个类的信息 + private static final Log logger = LogFactory.getLog(BackageFileController.class); + + @Value("${cbsd.uploadfilesrootpath}") + private String uploadfilesrootpath; + + /** + * 单文件上传 + */ + @ApiOperation(value = "单个文件上传", notes = "") + @PostMapping("/fileupload") + public Result oneFileUpload(MultipartFile file) { + if (file == null) { + return Result.error(ResultCode.PARAMETER_ERROR, "file文件参数未传"); + } + String fileName = file.getOriginalFilename(); + if (StringUtils.isBlank(fileName)) { + return Result.error("文件上传失败,请检查文件名称"); + } + try { + String suffix = fileName.substring(fileName.lastIndexOf(".")); + String fileId = UUIDUtils.getUUID(); + String parentDic = DateUtils.dateToString("yyyy/MM/dd"); + String filePath = parentDic + "/" + fileId + suffix; + //创建文件夹 + File parentFile = new File(uploadfilesrootpath, parentDic); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + Path savePath = Paths.get(uploadfilesrootpath, filePath); + Files.write(savePath, file.getBytes()); + + FileInfo fileInfo = new FileInfo(); + fileInfo.setNewFileName(fileId + suffix); + fileInfo.setOriginalFilename(fileName); + fileInfo.setRelativePath(parentDic); + fileInfo.setNginxFileReadPath(filePath); + fileInfo.setFileSize(String.valueOf(file.getSize())); + return Result.success(fileInfo); + } catch (Exception e) { + e.printStackTrace(); + } + return Result.error("文件上传失败"); + } + + + @ApiOperation(value = "多个文件上传", notes = "上传文件参数名称统一files," + + "如果中途有个别文件上传没有成功,将不会返回错误,只会返回对应正确上传文件信息列表," + + "前端请注意查看对比数量") + @PostMapping("/multifile") //ApiResultResponse> + public Result> multiFileUpload(List files) { + if (files == null || files.isEmpty()) { + return Result.error("文件不能为空"); + } + List fileInfoList = new ArrayList(); + for (MultipartFile file : files) { + Result result = oneFileUpload(file); + if (result.getCode() == ResultCode.OK && result.getData() != null) { + fileInfoList.add(result.getData()); + } + } + return Result.success(fileInfoList); + } + + /** + * 执行下载 + */ + @ApiOperation(value = "文件下载") + @GetMapping("download") //ApiResultResponse + public ResponseEntity download(@RequestParam String filePath, @RequestParam(required = false) String fileName) throws Exception { + + if (StringUtils.isBlank(filePath)) { + return null; + } + String downloadPath = uploadfilesrootpath + filePath; + File downloadFile = new File(downloadPath); + + HttpHeaders headers = new HttpHeaders(); + //下载 + headers.add("Content-Disposition", "attachment; filename=" + downloadFile.getName()); + //二进制流 + String mediaType = "application/octet-stream"; + return ResponseEntity.ok() + .headers(headers) + .contentLength(downloadFile.length()) + .contentType(MediaType.parseMediaType(mediaType)) + .body(new FileSystemResource(downloadFile)); + } + + + @ApiOperation(value = "文件下载或预览", httpMethod = "GET", notes = "文件下载或预览") + @ApiImplicitParams({ + @ApiImplicitParam(name = "filePath", value = "文件路劲", dataType = "String", dataTypeClass = String.class, paramType = "query"), + @ApiImplicitParam(name = "downloadOrRead", value = "下载或预览 :download/read", dataType = "String", dataTypeClass = String.class, paramType = "query") + }) + @RequestMapping("/fileDownLoad") + public ResponseEntity fileDownLoad( + @RequestParam String filePath, + @RequestParam(required = false) String fileName, + @RequestParam(required = false) String downloadOrRead) throws UnsupportedEncodingException { + + if (StringUtils.isBlank(filePath)) { + return ResponseEntity.badRequest().build(); + } + + String downloadPath = uploadfilesrootpath + filePath; + File downloadFile = new File(downloadPath); + + if (!downloadFile.exists()) { + return ResponseEntity.notFound().build(); + } + + String originalFileName = downloadFile.getName(); + String actualFileName = StringUtils.isNotBlank(fileName) ? fileName : originalFileName; + + // 如果用户传了文件名,但没有后缀,就补上原始后缀 + if (StringUtils.isNotBlank(fileName) && !fileName.contains(".")) { + String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); + actualFileName = fileName + suffix; + } + + // URL 编码文件名(防止中文乱码) + actualFileName = URLEncoder.encode(actualFileName, StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20"); + + HttpHeaders headers = new HttpHeaders(); + String mediaType; + + if ("read".equals(downloadOrRead)) { + // 预览 + headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline; filename*=UTF-8''" + actualFileName); + String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); + mediaType = MediaTypeUtils.getMediaType(suffix); // 你需要实现这个工具类 + } else { + // 下载 + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + actualFileName); + mediaType = "application/octet-stream"; + } + + return ResponseEntity.ok() + .headers(headers) + .contentLength(downloadFile.length()) + .contentType(MediaType.parseMediaType(mediaType)) + .body(new FileSystemResource(downloadFile)); + + } + + @GetMapping("/getVideo") + public void getVideo(HttpServletResponse response, String filePath, HttpServletRequest request) throws FileNotFoundException { + +// InputStream objectContent = OssUtils.ossGetInputStream( url) ; +// InputStream objectContent = new FileInputStream("/Users/chenlin/Downloads/123.mp4"); + String url = uploadfilesrootpath + filePath; + System.err.println(url); + try { + File tempFile = new File(url); + + RandomAccessFile randomFile = new RandomAccessFile(tempFile, "r");//只读模式 + long contentLength = randomFile.length(); + String range = request.getHeader("Range"); + int start = 0, end = 0; + if (range != null && range.startsWith("bytes=")) { + String[] values = range.split("=")[1].split("-"); + start = Integer.parseInt(values[0]); + if (values.length > 1) { + end = Integer.parseInt(values[1]); + } + } + int requestSize = 0; + if (end != 0 && end > start) { + requestSize = end - start + 1; + } else { + requestSize = Integer.MAX_VALUE; + } + + byte[] buffer = new byte[2048]; + response.setContentType("video/mp4"); + response.setHeader("Accept-Ranges", "bytes"); + String fileNameString = filePath.substring(filePath.lastIndexOf("/") + 1); + response.setHeader("ETag", fileNameString); + response.setHeader("Last-Modified", new Date().toString()); + //第一次请求只返回content length来让客户端请求多次实际数据 + if (range == null) { + + response.setHeader("Content-length", contentLength + ""); + } else { + //以后的多次以断点续传的方式来返回视频数据 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//206 + long requestStart = 0, requestEnd = 0; + String[] ranges = range.split("="); + if (ranges.length > 1) { + String[] rangeDatas = ranges[1].split("-"); + requestStart = Integer.parseInt(rangeDatas[0]); + if (rangeDatas.length > 1) { + requestEnd = Integer.parseInt(rangeDatas[1]); + } + } + long length = 0; + if (requestEnd > 0) { + length = requestEnd - requestStart + 1; + response.setHeader("Content-length", "" + length); + response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength); + } else { + length = contentLength - requestStart; + response.setHeader("Content-length", "" + length); + response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength); + } + } + ServletOutputStream out = response.getOutputStream(); + int needSize = requestSize; + randomFile.seek(start); + while (needSize > 0) { + + int len = randomFile.read(buffer); + if (needSize < buffer.length) { + out.write(buffer, 0, needSize); + } else { + out.write(buffer, 0, len); + if (len < buffer.length) { + break; + } + } + needSize -= buffer.length; + } + randomFile.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 下载保存时中文文件名的字符编码转换方法 + */ + public String toUTF8String(String str) { + StringBuffer sb = new StringBuffer(); + int len = str.length(); + for (int i = 0; i < len; i++) { + // 取出字符中的每个字符 + char c = str.charAt(i); + // Unicode码值为0~255时,不做处理 + if (c >= 0 && c <= 255) { + sb.append(c); + } else { // 转换 UTF-8 编码 + byte b[]; + try { + b = Character.toString(c).getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + b = null; + } + // 转换为%HH的字符串形式 + for (int j = 0; j < b.length; j++) { + int k = b[j]; + if (k < 0) { + k &= 255; + } + sb.append("%" + Integer.toHexString(k).toUpperCase()); + } + } + } + return sb.toString(); + } + + /** + * 获取上传文件的md5 + * + * @param file + * @return + * @throws IOException + */ + public String getMd5(MultipartFile file) { + try { + //获取文件的byte信息 + byte[] uploadBytes = file.getBytes(); + // 拿到一个MD5转换器 + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] digest = md5.digest(uploadBytes); + //转换为16进制 + return new BigInteger(1, digest).toString(16); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + /** + * 验证文件信息是否是 相关路径下的文件数据 + * + * @param relativeUrl 放开的相关路径 + * @param filePathUrl 下载文件路径 + * @return + */ + public static boolean isPathSafe(String relativeUrl, String filePathUrl) { + Path basePath = Paths.get(relativeUrl); + Path pathToCheck = Paths.get(filePathUrl); + return basePath.resolve(pathToCheck.normalize()).normalize().startsWith(basePath); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/BoardController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/BoardController.java new file mode 100644 index 0000000..d722810 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/BoardController.java @@ -0,0 +1,102 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.cbsd.universaltestsoftware_client.dto.BoardTemperatureDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelPageDto; +import com.cbsd.universaltestsoftware_client.dto.PageBoard; +import com.cbsd.universaltestsoftware_client.entity.Board; +import com.cbsd.universaltestsoftware_client.entity.Channel; +import com.cbsd.universaltestsoftware_client.service.BoardService; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Parameter; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.constraints.NotNull; + +/** + *

+ * 板卡 前端控制器 + *

+ * + * @author admin + * @since 2025-08-26 + */ +@RestController +@Api(tags = "板卡端接口") +@RequestMapping("/api/board") +public class BoardController { + @Resource + private BoardService boardService; + + @ApiOperation("获取板卡列表") + @PostMapping("/pageBoardList") + public String pageList(@RequestBody @Validated PageBoard pageBoard) { + return boardService.pageList(pageBoard); + } + + @ApiOperation("添加修改板卡") + @PostMapping("/saveOrUpdateBoard") + public String saveOrUpdateBoard(@RequestBody @Validated Board board) { + + return boardService.saveOrUpdateBoard(board); + } + + @ApiOperation("删除板卡") + @GetMapping("/deleteBoardById") + public String deleteById(@RequestParam String serverId, @RequestParam String boardId) { + return boardService.deleteById(serverId, boardId); + } + + @ApiOperation("获取板卡类型") + @GetMapping("/getBoardType") + public String getBoardType(@RequestParam String serverId) { + return boardService.getBoardType(serverId); + } + + @ApiOperation("新增或者修改通道") + @PostMapping("/saveOrUpdateChannel") + public String saveOrUpdateChannel(@RequestBody @Validated Channel channel) { + return boardService.saveOrUpdateChannel(channel); + } + + @ApiOperation("获取通道列表") + @PostMapping("/pageChannelList") + public Result pageChannelList(@RequestBody @Validated ChannelPageDto channelPageDto) { + return boardService.pageChannelList(channelPageDto); + } + + @ApiOperation("获取所有通道类型") + @GetMapping("/getChannelTypeAll") + public String getChannelTypeAll(@RequestParam String serverId, @Parameter(description = "boardTypeId") @NotNull(message = "板卡类型id不可以为空") String boardTypeId) { + return boardService.getChannelTypeAll(serverId, boardTypeId); + } + + + @ApiOperation("删除通道") + @GetMapping("/deleteChannelById") + public String deleteChannelById(@RequestParam String serverId, @RequestParam String channelId) { + return boardService.deleteChannelById(serverId, channelId); + } + + @ApiOperation("根据版卡id获取通道的路径") + @GetMapping("/getChannelPath") + public String getChannelPath(@RequestParam String serverId, @RequestParam String boardId) { + return boardService.getChannelPath(serverId, boardId); + } + + @ApiOperation("刷新绑卡信息") + @GetMapping("/refressBoardInformation") + public String refressBoardInformation(@RequestParam String serverId) { + return boardService.refressBoardInformation(serverId); + } + + @ApiOperation("获取板卡温度") + @PostMapping("/getBoardTemperature") + public String getBoardTemperature(@RequestBody BoardTemperatureDto dto) { + return boardService.getBoardTemperature(dto); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/BootLogController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/BootLogController.java new file mode 100644 index 0000000..f0d545b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/BootLogController.java @@ -0,0 +1,50 @@ +package com.cbsd.universaltestsoftware_client.controller; + + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.BootLogDeleteDto; +import com.cbsd.universaltestsoftware_client.dto.BootLogPageDto; +import com.cbsd.universaltestsoftware_client.entity.BootLog; +import com.cbsd.universaltestsoftware_client.service.IBootLogService; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + *

+ * 前端控制器 + *

+ * + * @author wt + * @since 2025-09-03 + */ +@RestController +@Api(tags = "日志记录") +@RequestMapping("/api/boot-log") +public class BootLogController { + + @Resource + private IBootLogService bootLogService; + + + @ApiOperation(value = "日志记录-查询分页") + @PostMapping("/bootLogPage") + public Result> bootLogPage(@RequestBody @Validated BootLogPageDto bootLogPageDto) { + return bootLogService.bootLogPage(bootLogPageDto); + } + + + @ApiOperation(value = "日志记录-删除分页") + @PostMapping("/bootLogDelete") + public Result bootLogDelete(@RequestBody @Validated BootLogDeleteDto bootLogDeleteDto) { + return bootLogService.bootLogDelete(bootLogDeleteDto); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/ChannelDataController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ChannelDataController.java new file mode 100644 index 0000000..5790f49 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ChannelDataController.java @@ -0,0 +1,83 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataHistoryDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataStatisticsDto; +import com.cbsd.universaltestsoftware_client.dto.PageChannelDataDto; +import com.cbsd.universaltestsoftware_client.entity.ChannelDataHistory; +import com.cbsd.universaltestsoftware_client.parser.model.FiledParseData; +import com.cbsd.universaltestsoftware_client.service.ChannelDataService; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.ChannelDataStatisticsVo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.validation.constraints.NotBlank; +import java.util.List; + +@RestController +@Api(tags = "数据回访") +@RequestMapping("/api/channelData") +public class ChannelDataController { + + @Resource + private ChannelDataService channelDataService; + + @PostConstruct + public void init(){ + channelDataService.clearChannelData(); + } + + + @ApiOperation("开始数据拉取") + @PostMapping("/start") + public Result start(@RequestBody @Validated PageChannelDataDto pageChannelDataDto) throws Exception { + // 调用服务层的start方法,开始数据拉取 + channelDataService.start(pageChannelDataDto); + return Result.success(); + } + + @ApiOperation("停止数据拉取") + @GetMapping("/stop") + public Result stop(@NotBlank @RequestParam String dataBoardId) { + // 调用服务层的stop方法,停止指定dataBoardId的数据拉取 + channelDataService.stop(dataBoardId); + return Result.success(); + } + + @ApiOperation("更新分页参数") + @PostMapping("/updateData") + public Result updateData(@RequestBody @Validated PageChannelDataDto pageChannelDataDto) { + // 调用服务层的update方法,更新分页参数 + channelDataService.updateData(pageChannelDataDto); + return Result.success(); + } + + @ApiOperation(value = "通道历史数据-查询分页") + @PostMapping("/channelDataQuery") + public String channelDataQuery(@RequestBody @Validated ChannelDataHistoryDto channelDataHistoryDto) { + return channelDataService.channelDataQuery(channelDataHistoryDto); + } + + @ApiOperation(value = "通道历史数据-删除") + @PostMapping("/channelDataDelete") + public String channelDataDelete(@RequestBody @Validated ChannelDataHistoryDto channelDataHistoryDto) { + return channelDataService.channelDataDelete(channelDataHistoryDto); + } + + @ApiOperation(value = "通道数据统计") + @PostMapping("/getChannelDataStatistics") + public Result> getChannelDataStatistics(@RequestBody ChannelDataStatisticsDto dto) { + return channelDataService.getChannelDataStatistics(dto); + } + @ApiOperation(value = "清除通道数据统计") + @PostMapping("/clearChannelDataStatistics") + public Result clearChannelDataStatistics(@RequestBody ChannelDataStatisticsDto dto) { + return channelDataService.clearChannelDataStatistics(dto); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/DataBoardChannelController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/DataBoardChannelController.java new file mode 100644 index 0000000..5c3889b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/DataBoardChannelController.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 前端控制器 + *

+ * + * @author admin + * @since 2025-09-10 + */ +@RestController +@RequestMapping("/dataBoardChannel") +public class DataBoardChannelController { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/DataBoardController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/DataBoardController.java new file mode 100644 index 0000000..e8e7731 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/DataBoardController.java @@ -0,0 +1,161 @@ +package com.cbsd.universaltestsoftware_client.controller; + + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.DataClearDto; +import com.cbsd.universaltestsoftware_client.dto.DownloadDto; +import com.cbsd.universaltestsoftware_client.dto.ExportDataBoardDto; +import com.cbsd.universaltestsoftware_client.dto.InsertDataBoardDto; +import com.cbsd.universaltestsoftware_client.entity.DataBoard; +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import com.cbsd.universaltestsoftware_client.entity.Download; +import com.cbsd.universaltestsoftware_client.service.DataBoardService; +import com.cbsd.universaltestsoftware_client.service.SystemConfigService; +import com.cbsd.universaltestsoftware_client.util.MediaTypeUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + *

+ * 数据看板 前端控制器 + *

+ * + * @author admin + * @since 2025-09-10 + */ +@RestController +@Api(tags = "数据看板") +@RequestMapping("/api/dataBoard") +public class DataBoardController { + + @Resource + private DataBoardService dataBoardService; + + @Value("${cbsd.downloadUrl}") + private String downloadDir; + + @ApiOperation("新增修改数据看板") + @PostMapping("/saveOrUpdateDataBoard") + public Result saveOrUpdateDataBoard(@RequestBody @Validated InsertDataBoardDto insertDataBoardDto) throws Exception { + dataBoardService.saveOrUpdateDataBoard(insertDataBoardDto); + return Result.success(); + } + + @ApiOperation("删除数据看板") + @GetMapping("/removeDataBoardById") + public Result removeDataBoardById(@RequestParam String dataBoardId) throws Exception { + dataBoardService.removeDataBoardById(dataBoardId); + return Result.success(); + } + + @ApiOperation("查询数据看板") + @GetMapping("/listDataBoard") + public Result> listDataBoard(@RequestParam Integer dataType) throws Exception { + return Result.success(dataBoardService.listDataBoard(dataType)); + } + + @ApiOperation("通过id修改颜色或者左轴") + @PostMapping("/updateDataBoardById") + public Result updateDataBoardById(@RequestBody DataBoardChannel dataBoardChannel) throws Exception { + dataBoardService.updateDataBoardById(dataBoardChannel); + return Result.success(); + } + + @ApiOperation("导出数据回访") + @PostMapping("/exportDataBoard") + public Result exportDataBoard(@RequestBody @Validated ExportDataBoardDto exportDataBoardDto, HttpServletResponse response) throws Exception { + return dataBoardService.exportDataBoard(exportDataBoardDto, response); + + } + + @ApiOperation("清除") + @PostMapping("/clear") + public Result clear(@RequestBody @Validated ExportDataBoardDto exportDataBoardDto) throws Exception { + dataBoardService.clear(exportDataBoardDto); + return Result.success(); + } + + @ApiOperation("导出历史查看") + @PostMapping("/exportHistory") + public Result> exportHistory(@RequestBody @Validated DownloadDto downloadDto) { + return dataBoardService.exportHistory(downloadDto); + + } + + @ApiOperation(value = "文件下载或预览", httpMethod = "GET", notes = "文件下载或预览") + @ApiImplicitParams({ + @ApiImplicitParam(name = "filePath", value = "文件路劲", dataType = "String", dataTypeClass = String.class, paramType = "query"), + @ApiImplicitParam(name = "downloadOrRead", value = "下载或预览 :download/read", dataType = "String", dataTypeClass = String.class, paramType = "query") + }) + @RequestMapping("/fileDownLoad") + public ResponseEntity fileDownLoad( + @RequestParam String filePath, + @RequestParam(required = false) String fileName, + @RequestParam(required = false) String downloadOrRead) throws UnsupportedEncodingException { + + if (StringUtils.isBlank(filePath)) { + return ResponseEntity.badRequest().build(); + } + + String downloadPath = downloadDir + "/"+filePath; + File downloadFile = new File(downloadPath); + + if (!downloadFile.exists()) { + return ResponseEntity.notFound().build(); + } + + String originalFileName = downloadFile.getName(); + String actualFileName = StringUtils.isNotBlank(fileName) ? fileName : originalFileName; + + // 如果用户传了文件名,但没有后缀,就补上原始后缀 + if (StringUtils.isNotBlank(fileName) && !fileName.contains(".")) { + String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); + actualFileName = fileName + suffix; + } + + // URL 编码文件名(防止中文乱码) + actualFileName = URLEncoder.encode(actualFileName, StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20"); + + HttpHeaders headers = new HttpHeaders(); + String mediaType; + + if ("read".equals(downloadOrRead)) { + // 预览 + headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline; filename*=UTF-8''" + actualFileName); + String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); + mediaType = MediaTypeUtils.getMediaType(suffix); // 你需要实现这个工具类 + } else { + // 下载 + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + actualFileName); + mediaType = "application/octet-stream"; + } + + return ResponseEntity.ok() + .headers(headers) + .contentLength(downloadFile.length()) + .contentType(MediaType.parseMediaType(mediaType)) + .body(new FileSystemResource(downloadFile)); + + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/DownloadController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/DownloadController.java new file mode 100644 index 0000000..3261d95 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/DownloadController.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 前端控制器 + *

+ * + * @author admin + * @since 2026-02-03 + */ +@RestController +@RequestMapping("/download") +public class DownloadController { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/InstructController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/InstructController.java new file mode 100644 index 0000000..104d852 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/InstructController.java @@ -0,0 +1,224 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.*; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.service.InstructService; +import com.cbsd.universaltestsoftware_client.util.CalcUtils; +import com.cbsd.universaltestsoftware_client.util.DataUtils; +import com.cbsd.universaltestsoftware_client.util.ModbusCRC16Utils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.ServerListVo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@RestController +@Api(tags = "指令") +@RequestMapping("/api/instruct") +public class InstructController { + + @Resource + private InstructService instructService; + + @ApiOperation("获取指令列表") + @PostMapping("/pageInstructList") + public Result> pageList(@RequestBody @Validated InstructPageDto instructPageDto) { + return Result.success(instructService.pageList(instructPageDto)); + } + + @ApiOperation("修改指令") + @PostMapping("/saveOrUpdateInstruct") + public Result saveOrUpdateInstruct(@RequestBody @Validated Instruct instruct) { + return instructService.saveOrUpdateInstruct(instruct); + } + + @ApiOperation("删除指令") + @PostMapping("/deleteInstructById") + public Result deleteInstructById(@RequestBody InstructDeleteDto dto) { + return instructService.deleteInstructById(dto); + } + + @ApiOperation("执行指令") + @GetMapping("/execute") + public Result execute(@RequestParam String instructId) { +// return instructService.execute(instructId, null); + return null; + } + + @ApiOperation("获取通道列表") + @GetMapping("/getChannelList") + public Result> getChannelList(String keyword) { + return instructService.getChannelList(keyword); + } + + @ApiOperation("获取板载驱动程序注册函数") + @GetMapping("/getChannelFunctionName") + public String getChannelFunctionName(@RequestParam String channelId, @RequestParam String serverId) { + return instructService.getChannelFunctionName(channelId, serverId); + } + + @ApiOperation("获取板载驱动额外数据") + @GetMapping("/getBoardDriverExtraData") + public String getBoardDriverExtraData(@RequestParam String channelId, @RequestParam String serverId) { + return instructService.getBoardDriverExtraData(channelId, serverId); + } + + + @ApiOperation("新增") + @PostMapping("/saveOrUpdateInstructGroup") + public Result saveOrUpdateInstructGroup(@RequestBody @Validated InstructGroupDto instructGroupDto) { + return instructService.saveOrUpdateInstructGroup(instructGroupDto); + } + + @ApiOperation("上移") + @GetMapping("/moveUp") + public Result moveUp(@RequestParam String instructId) { + return instructService.moveUp(instructId); + + } + + @ApiOperation("下移") + @GetMapping("/moveDown") + public Result moveDown(@RequestParam String instructId) { + return instructService.moveDown(instructId); + } + + @ApiOperation("根据指令id获取协议参数") + @GetMapping("/getInstructProtocolParam") + public Result> getInstructProtocolParam(@RequestParam String instructId) { + return instructService.getInstructProtocolParam(instructId); + } + + /** + * 至少 2 个常规指令 → 新建组并归组 + */ + @ApiOperation("新建指令组:至少 2 个常规指令 → 新建组并归组") + @PostMapping("/createGroupFromList") + public Result createGroupFromList(@Valid @RequestBody CreateGroupDTO dto) { + instructService.createGroupFromList(dto); + return Result.success(); + } + + @ApiOperation("批量修改指令参数") + @PostMapping("/batchChangeParam") + public Result batchChangeParam(@RequestBody BatchChangeInstructParamDto dto) { + return instructService.batchChangeParam(dto); + } + + + @ApiOperation("批量复制") + @PostMapping("/batchCopyBelow") + public Result batchCopyBelow(@RequestBody @Validated BatchCopyBelowDTO dto) { + instructService.batchCopyBelow(dto); + return Result.success(); + } + + @ApiOperation("批量移动") + @PostMapping("/batchMoveBelow") + public Result batchMoveBelow(@RequestBody @Validated BatchMoveBelowDTO dto) { + instructService.batchMoveBelow(dto); + return Result.success(); + } + + @ApiOperation("上传指令文件") + @PostMapping("/uploadCommandFile") + public Result uploadCommandFile(@RequestParam String instructId, MultipartFile file) { + return instructService.uploadCommandFile(instructId, file); + } + + @ApiOperation("删除指令脚本文件") + @PostMapping("/deleteInstructFile") + public Result deleteInstructFile(@RequestBody InstructDeleteFileDto dto) { + return instructService.deleteInstructFile(dto); + } + + @ApiOperation("获取指令校验位") + @PostMapping("/instructCalc") + public Result instructCalc(@RequestBody InstructCalcDto dto) { + if (dto == null) { + return Result.error("参数不能为空"); + } + if (StringUtils.isBlank(dto.getData())) { + return Result.error("数据不能为空"); + } + if (dto.getCalcMethod() == null) { + return Result.error("校验方法不能为空"); + } + + String calcResult; + if (dto.getCalcMethod() == 1) { + // crc16 + calcResult = ModbusCRC16Utils.calculateCRCToHexString( + DataUtils.HexToByteArr(dto.getData())); + } else if (dto.getCalcMethod() == 2) { + // crc32 + long result = CalcUtils.crc32Checksum( + DataUtils.HexToByteArr(dto.getData())); + calcResult = Long.toHexString(result).toUpperCase(); + } else if (dto.getCalcMethod() == 3) { + // 奇偶校验 + byte result = CalcUtils.parityCheck( + DataUtils.HexToByteArr(dto.getData())); + calcResult = Integer.toHexString(result).toUpperCase(); + } else if (dto.getCalcMethod() == 4) { + // 校验和 + int result = CalcUtils.simpleChecksum( + DataUtils.HexToByteArr(dto.getData())); + calcResult = Integer.toHexString(result).toUpperCase(); + } else if (dto.getCalcMethod() == 5) { + // MD5 + try { + calcResult = CalcUtils.md5Checksum( + DataUtils.HexToByteArr(dto.getData())).toUpperCase(); + } catch (NoSuchAlgorithmException e) { + return Result.error("计算失败"); + } + } else if (dto.getCalcMethod() == 6) { + // SHA-256 + try { + calcResult = CalcUtils.sha256Checksum( + DataUtils.HexToByteArr(dto.getData())).toUpperCase(); + } catch (NoSuchAlgorithmException e) { + return Result.error("计算失败"); + } + } else if (dto.getCalcMethod() == 7) { + // 单字节异或校验 + try { + calcResult = CalcUtils.byteXOR(dto.getData()).toUpperCase(); + } catch (Exception e) { + return Result.error("计算失败"); + } + } else { + return Result.error("不支持的校验方式"); + } + + // ====== 根据 byteCount 截取或补齐 ====== + calcResult = CalcUtils.fitToByteCount(calcResult, dto.getByteCount()); + return Result.success(calcResult); + } + + @ApiOperation("获取指令数据注释") + @GetMapping("/getInstructDataRemark") + public String getInstructDataRemark(@RequestParam String instructId) { + return instructService.getInstructDataRemark(instructId); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/InvocationController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/InvocationController.java new file mode 100644 index 0000000..b6e92fe --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/InvocationController.java @@ -0,0 +1,93 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.InvocationPageDto; +import com.cbsd.universaltestsoftware_client.dto.InvocationSortDto; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.cbsd.universaltestsoftware_client.service.InvocationService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + *

+ * 用列 前端控制器 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@RestController +@Api(tags = "用列") +@RequestMapping("/api/invocation") +public class InvocationController { + + @Resource + private InvocationService invocationService; + + @ApiOperation("获取用列列表") + @PostMapping("/pageList") + public Result> pageList(@RequestBody @Validated InvocationPageDto invocationPageDto) { + return Result.success(invocationService.pageList(invocationPageDto)); + } + + @ApiOperation("添加或者修改用列") + @PostMapping("/saveOrUpdate") + public Result saveOrUpdate(@RequestBody @Validated Invocation invocation) { + if (StringUtils.isNotBlank(invocation.getInvocationId())) { + invocation.setUpdateTime(DateUtils.dateToString()); + } else { + long schemeInvocationCount = invocationService.count(new LambdaQueryWrapper().eq(Invocation::getSchemeId, invocation.getSchemeId())); + invocation.setSort((int) (schemeInvocationCount + 1)); + invocation.setCreateTime(DateUtils.dateToString()); + } + try { + return invocationService.saveOrUpdate(invocation) ? Result.success("保存成功") : Result.error("保存失败"); + } catch (Exception e) { + if (e instanceof DuplicateKeyException) { + return Result.error("同一方案内用例名字重复"); + } + return Result.error("保存失败"); + } + } + + @ApiOperation("删除用列") + @GetMapping("/deleteById") + public Result deleteById(@RequestParam String invocationId) { + return invocationService.deleteById(invocationId); + } + + @ApiOperation("排序") + @PostMapping("/sort") + public Result sort(@RequestBody InvocationSortDto dto){ + return invocationService.sort(dto); + } + + @ApiOperation("用列导出") + @GetMapping("/exportInvocation") + public Result exportInvocation(@RequestParam String invocationId, HttpServletResponse response)throws IOException { + invocationService.exportInvocation(invocationId, response); + return Result.success(); + } + + /** + * 导入 XML 文件 + */ + @PostMapping("/importXml") + public Result importXml(@RequestParam("file") MultipartFile file,@RequestParam("schemeId")String schemeId) throws Exception { + invocationService.importXml(file,schemeId); + return Result.success(); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/JavaCompilerController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/JavaCompilerController.java new file mode 100644 index 0000000..1114d94 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/JavaCompilerController.java @@ -0,0 +1,100 @@ +package com.cbsd.universaltestsoftware_client.controller; + + + +import com.cbsd.client.Test; +import com.cbsd.universaltestsoftware_client.DynamicClassBootstrap; +import com.cbsd.universaltestsoftware_client.dto.SubmitAndCompile; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import com.cbsd.universaltestsoftware_client.service.JavaCompileService; + +@RestController +@RequestMapping("/api/javaCompiler") +public class JavaCompilerController { + + private final JavaCompileService javaCompileService; + + @Autowired + public JavaCompilerController(JavaCompileService javaCompileService) { + this.javaCompileService = javaCompileService; + } + + /** + * 上传Java文件并编译 + */ + @ApiModelProperty("上传Java文件并编译") + @PostMapping("/uploadCompile") + public Result uploadAndCompile(@RequestParam("file") MultipartFile file) { + // 验证文件 + if (file.isEmpty()) { + return Result.error("请上传有效的Java文件"); + + } + + String fileName = file.getOriginalFilename(); + if (fileName == null || !fileName.endsWith(".java")) { + return Result.error("请上传扩展名为.java的文件"); + + } + + try { + Result stringResult = javaCompileService.compileJavaFile(file); + return stringResult; + } catch (Exception e) { + return Result.error("编译过程出错"+e.getMessage()); + + } + } + + /** + * 提交Java代码字符串并编译 + */ + @ApiOperation("提交Java代码字符串并编译") + @PostMapping("/submitCompile") + public Result submitAndCompile(@RequestBody @Validated SubmitAndCompile submitAndCompile) { + if (submitAndCompile.getClassName() == null || submitAndCompile.getClassName().trim().isEmpty()) { + return Result.error("类名不能为空"); + + } + + if (submitAndCompile.getCode() == null || submitAndCompile.getCode().trim().isEmpty()) { + return Result.error("Java代码不能为空"); + } + + try { + Result stringResult = javaCompileService.compileJavaCode(submitAndCompile.getClassName(), submitAndCompile.getCode()); + return stringResult; + } catch (Exception e) { + return Result.error("编译过程出错"+e.getMessage()); + } + } + @ApiOperation("获取所有已编译的类") + @GetMapping("/getClasses") + public Result getAllCompiledClasses() { + return Result.success(DynamicClassBootstrap.CLAZZ_CACHE); + + } + @ApiOperation("测试执行其中一个clss结果") + @GetMapping("/testExecuteClass") + public Result testExecuteClass(@RequestParam("className") String className) { + Class aClass = DynamicClassBootstrap.CLAZZ_CACHE.get(className); + if (aClass != null) { + try { + Test o = (Test) aClass.newInstance(); + return Result.success(o.test()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return Result.success(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/LogController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/LogController.java new file mode 100644 index 0000000..201410d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/LogController.java @@ -0,0 +1,78 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import cn.hutool.http.HttpRequest; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.cbsd.universaltestsoftware_client.dto.BootLogDeleteDto; +import com.cbsd.universaltestsoftware_client.dto.PageLogDto; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.mapper.ServerMapper; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +@RestController +@Api(tags = "服务端日志") +@RequestMapping("/api/log") +public class LogController { + + @Resource + private ServerMapper serverMapper; + @ApiOperation("通过服务端id获取日志") + @PostMapping("/getLogList") + public String getLogList(@RequestBody @Validated PageLogDto pageLogDto) { + + ServerInfo serverInfo = serverMapper.selectById(pageLogDto.getServerId()); + if (serverInfo==null){ + throw new RuntimeException("服务端不存在"); + } + try { + String url = "http://"+serverInfo.getIpAddress()+":"+serverInfo.getHttpPort()+"/universaltestsoftware_server_api/outer/client/bootLogPage"; + String result = HttpRequest.post(url) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(pageLogDto)) + .execute() + .body(); + + if (StringUtils.isBlank(result)){ + throw new RuntimeException("连接失败"); + } + + return result; + }catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException("连接失败"); + } + } + + @ApiOperation("通过服务端id删除日志") + @PostMapping("/deleteLog") + private String deleteLog(@RequestBody BootLogDeleteDto bootLogDeleteDto) { + ServerInfo serverInfo = serverMapper.selectById(bootLogDeleteDto.getServerId()); + if (serverInfo==null){ + throw new RuntimeException("服务端不存在"); + } + try { + String url = "http://"+serverInfo.getIpAddress()+":"+serverInfo.getHttpPort()+"/universaltestsoftware_server_api/outer/client/bootLogDelete"; + String result = HttpRequest.post(url) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(bootLogDeleteDto)) + .execute() + .body(); + + if (StringUtils.isBlank(result)){ + throw new RuntimeException("连接失败"); + } + + return result; + }catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException("连接失败"); + } + } + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/ParserController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ParserController.java new file mode 100644 index 0000000..d8efb5b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ParserController.java @@ -0,0 +1,62 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.cbsd.universaltestsoftware_client.entity.Parser; +import com.cbsd.universaltestsoftware_client.entity.ParserProtocol; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.ReceiveTcpContent; +import com.cbsd.universaltestsoftware_client.service.ParserService; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@RestController +@Api(tags = "解析器管理") +@RequestMapping("/api/parser") +public class ParserController { + + @Resource + private ParserService parserService; + + + @ApiOperation("获取解析器列表") + @GetMapping("/getParserList") + public Result> getParserList(@RequestParam String channelId) { + return parserService.getParserList(channelId); + } + + @ApiOperation("修改或者新增解析器") + @PostMapping("/saveOrUpdateParser") + public Result saveOrUpdateParser(@RequestBody Parser parser) { + return parserService.saveOrUpdateParser(parser); + } + + @ApiOperation("删除解析器") + @GetMapping("/deleteParserById") + public Result deleteParserById(@RequestParam String parserId) { + return parserService.deleteParserById(parserId); + } + + @ApiOperation("解析器添加或者修改协议") + @PostMapping("/addProtocol") + public Result addProtocol(@RequestBody @Validated List parserProtocol) { + return parserService.addProtocol(parserProtocol); + } + @ApiOperation("解析器协议列表") + @GetMapping("/getProtocolList") + public Result> getProtocolList(@RequestParam String parserId) { + return parserService.getProtocolList(parserId); + } + + @ApiOperation("删除解析器协议") + @GetMapping("/deleteParserProtocolById") + public Result deleteParserProtocolById(@RequestParam String parserProtocolId) { + return parserService.deleteParserProtocolById(parserProtocolId); + } + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/ProtocolController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ProtocolController.java new file mode 100644 index 0000000..5b6cced --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ProtocolController.java @@ -0,0 +1,68 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.ProtocolPageDto; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.cbsd.universaltestsoftware_client.service.ProtocolService; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.ProtocolSaveVo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +/** + *

+ * 协议 前端控制器 + *

+ * + * @author admin + * @since 2025-09-02 + */ +@RestController +@Api(tags = "协议管理") +@RequestMapping("/api/protocol") +public class ProtocolController { + @Resource + private ProtocolService protocolService; + + @ApiOperation("获取协议列表") + @PostMapping("/pageListProtocol") + public Result> pageListProtocol(@RequestBody ProtocolPageDto dto) { + return protocolService.pageListProtocol(dto); + } + + @ApiOperation("添加或者修改协议") + @PostMapping("/saveOrUpdateProtocol") + public Result saveOrUpdateProtocol(@RequestBody Protocol protocol) { + + return protocolService.saveOrUpdateProtocol(protocol); + } + + @ApiOperation("删除协议") + @GetMapping("/deleteProtocolById") + public Result deleteProtocolById(@RequestParam String protocolId) { + return protocolService.deleteProtocolById(protocolId); + } + + @ApiOperation("协议导入") + @PostMapping("/import") + public Result importProtocol(MultipartFile file) { + if (file == null) { + return Result.error("文件不能为空"); + } + if (file.isEmpty()) { + return Result.error("文件无效或文件内容为空"); + } + if (StringUtils.isBlank(file.getOriginalFilename())) { + return Result.error("文件名无效"); + } + if (!(file.getOriginalFilename().endsWith(".xlsx") || file.getOriginalFilename().endsWith(".xls"))) { + return Result.error("文件格式错误"); + } + return protocolService.importProtocol(file); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/SchemeController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/SchemeController.java new file mode 100644 index 0000000..61ddc81 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/SchemeController.java @@ -0,0 +1,59 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Scheme; +import com.cbsd.universaltestsoftware_client.service.SchemeService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + *

+ * 方案 前端控制器 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@RestController +@Api(tags = "方案") +@RequestMapping("/api/scheme") +public class SchemeController { + + @Resource + private SchemeService schemeService; + + + @PostMapping("/pageList") + @ApiOperation("获取方案列表") + public Result> pageList(@RequestBody BasePage basePage) { + return schemeService.pageList(basePage); + } + + @ApiOperation("添加或者修改方案") + @PostMapping("/saveOrUpdate") + public Result saveOrUpdate(@RequestBody @Validated Scheme scheme) { + if (StringUtils.isNotBlank(scheme.getSchemeId())) { + scheme.setUpdateTime(DateUtils.dateToString()); + } else { + scheme.setCreateTime(DateUtils.dateToString()); + } + return schemeService.saveOrUpdate(scheme) ? Result.success("成功") : Result.error("失败"); + + } + + @GetMapping("/deleteById") + @ApiOperation("删除方案") + public Result deleteById(@RequestParam String schemeId) { + return schemeService.deleteScheme(schemeId); + } + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/ScriptController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ScriptController.java new file mode 100644 index 0000000..02d8da9 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ScriptController.java @@ -0,0 +1,180 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.cbsd.universaltestsoftware_client.dto.ScriptContentDto; +import com.cbsd.universaltestsoftware_client.dto.ScriptDto; +import com.cbsd.universaltestsoftware_client.dto.ScriptFileContentDto; +import com.cbsd.universaltestsoftware_client.script.ScriptManager; +import com.cbsd.universaltestsoftware_client.service.InstructService; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.InstructScriptVo; +import com.cbsd.universaltestsoftware_client.vo.ScriptTypeVo; +import com.cbsd.universaltestsoftware_client.vo.ScriptVo; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +@RestController +@Api(tags = "脚本管理") +@RequestMapping("/api/script") +public class ScriptController { + + @Resource + private ScriptManager scriptManager; + + @Resource + private InstructService instructService; + + @ApiOperation("刷新脚本") + @GetMapping("/recompileAllScript") + public Result recompileAllScript() { + scriptManager.recompileAllScript(); + return Result.success(); + } + + @ApiOperation("获取脚本类型列表") + @GetMapping("/getScriptTypeList") + public Result> getScriptTypeList() { + return Result.success(scriptManager.getScriptTypeList()); + } + + @ApiOperation("根据脚本类型获取脚本列表") + @GetMapping("/getScriptList") + public Result> getScriptList(@RequestParam String scriptType) { + //判断scriptType是否是ScriptManager.ScriptType + if (StringUtils.isBlank(scriptType)) { + return Result.error("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(scriptType)) { + return Result.error("类型错误"); + } + return Result.success(scriptManager.getScriptList(scriptType)); + } + + @ApiOperation("编译脚本") + @PostMapping("/compileScript") + public Result compileScript(@RequestBody ScriptDto dto) { + if (dto == null) { + return Result.error("参数错误"); + } + if (StringUtils.isBlank(dto.getScriptType())) { + return Result.error("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(dto.getScriptType())) { + return Result.error("类型错误"); + } + if (StringUtils.isBlank(dto.getScriptFileName())) { + return Result.error("脚本文件名称不能为空"); + } + return scriptManager.compileScript(dto.getScriptType(), dto.getScriptFileName()); + } + + @ApiOperation("删除脚本") + @PostMapping("/deleteScript") + public Result deleteScript(@RequestBody ScriptDto dto) { + if (dto == null) { + return Result.error("参数错误"); + } + if (StringUtils.isBlank(dto.getScriptType())) { + return Result.error("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(dto.getScriptType())) { + return Result.error("类型错误"); + } + if (StringUtils.isBlank(dto.getScriptFileName())) { + return Result.error("脚本文件名称不能为空"); + } + scriptManager.deleteScript(dto.getScriptType(), dto.getScriptFileName(), dto.getScriptName()); + return Result.success(); + } + + @ApiOperation("上传脚本") + @PostMapping("/uploadScript") + public Result uploadScript(@RequestParam String instructId, @RequestParam String scriptType, MultipartFile file) { + InstructScriptVo vo = null; + try { + vo = scriptManager.uploadScript(scriptType, file); + instructService.uploadScript(instructId, vo.getScriptType(), vo.getScriptFile()); + vo.setInstructId(instructId); + return Result.success(vo); + } catch (Exception e) { + if (vo != null) { + scriptManager.deleteScriptFile(vo.getScriptType(), vo.getScriptFile()); + } + return Result.error(e.getMessage()); + } + } + + @ApiOperation("保存脚本内容") + @PostMapping("/saveScriptContent") + public Result saveScriptContent(@RequestBody ScriptContentDto dto) { + InstructScriptVo vo = null; + try { + vo = scriptManager.saveScriptContent(dto.getInstructId(), dto.getScriptType(), dto.getScriptContent()); + instructService.uploadScriptContent(dto.getInstructId(), vo.getScriptType(), vo.getScriptName(), dto.getScriptContent()); + vo.setInstructId(dto.getInstructId()); + return Result.success(vo); + } catch (Exception e) { + if (vo != null) { + scriptManager.deleteScriptFile(vo.getScriptType(), vo.getScriptFile()); + } + return Result.error(e.getMessage()); + } + } + + @ApiOperation("获取脚本文件内容") + @GetMapping("/getScriptFileContent") + public Result getScriptFileContent(@RequestParam String filePath) { + if (StringUtils.isBlank(filePath)) { + return Result.error("文件地址不能为空"); + } + if (!new File(filePath).exists()) { + return Result.error("文件不存在"); + } + try { + String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); + return Result.success(content); + } catch (IOException e) { + e.printStackTrace(); + } + return Result.error("文件读取失败"); + } + + @ApiOperation("保存脚本文件内容") + @PostMapping("/saveScriptFileContent") + public Result saveScriptFileContent(@RequestBody ScriptFileContentDto dto) { + if (StringUtils.isBlank(dto.getScriptType())) { + return Result.error("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(dto.getScriptType())) { + return Result.error("类型错误"); + } + if (StringUtils.isBlank(dto.getContent())) { + return Result.error("脚本内容不能为空"); + } + try { + if (StringUtils.isBlank(dto.getFilePath())) { + //新增 + scriptManager.createScriptFile(dto.getScriptType(), dto.getContent()); + } else { + //修改 + File file = new File(dto.getFilePath()); + Files.write(Paths.get(file.getPath()), dto.getContent().getBytes(StandardCharsets.UTF_8)); + scriptManager.compileScriptFile(dto.getScriptType(), file); + } + return Result.success(); + }catch (Exception e) { + return Result.error(e.getMessage()); + } + + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/SensorEquipmentController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/SensorEquipmentController.java new file mode 100644 index 0000000..c6bb312 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/SensorEquipmentController.java @@ -0,0 +1,47 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.SensorEquipmentDto; +import com.cbsd.universaltestsoftware_client.dto.SensorEquipmentPageDto; +import com.cbsd.universaltestsoftware_client.entity.SensorEquipment; +import com.cbsd.universaltestsoftware_client.service.SensorEquipmentService; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@RestController +@Api(tags = "传感设备") +@RequestMapping("/api/sensorEquipment") +public class SensorEquipmentController { + + @Resource + private SensorEquipmentService sensorEquipmentService; + + @ApiOperation("分页列表") + @PostMapping("/pageList") + public Result> pageList(@RequestBody SensorEquipmentPageDto dto) { + return sensorEquipmentService.pageList(dto); + } + + @ApiOperation("获取所有列表") + @GetMapping("/getAllList") + public Result> getAllList(@RequestParam(required = false) String channelId) { + return sensorEquipmentService.getAllList(channelId); + } + + @ApiOperation("新增或修改") + @PostMapping("/addOrUpdate") + public Result addOrUpdate(@RequestBody SensorEquipmentDto dto){ + return sensorEquipmentService.addOrUpdate(dto); + } + + @ApiOperation("删除") + @GetMapping("/delete") + public Result delete(@RequestParam String equipmentId) { + return sensorEquipmentService.delete(equipmentId); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/ServerTerminalController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ServerTerminalController.java new file mode 100644 index 0000000..29e0cf8 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/ServerTerminalController.java @@ -0,0 +1,76 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.PageServerInfoDto; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.service.ServeService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + *

+ * 服务端 前端控制器 + *

+ * + * @author admin + * @since 2025-08-26 + */ +@RestController +@Api(tags = "服务端接口") +@RequestMapping("/api/serverTerminal") +public class ServerTerminalController { + @Resource + private ServeService serveService; + + @ApiOperation("获取服务端列表") + @PostMapping("/pageList") + public Result> pageList(@RequestBody PageServerInfoDto basePage) { + return serveService.pageList(basePage); + } + + @ApiOperation("添加或者修改服务端") + @PostMapping("/saveOrUpdate") + public Result saveOrUpdate(@RequestBody @Validated ServerInfo serverInfo) { + if (serverInfo.getServerId() == null) { + serverInfo.setCreateTime(DateUtils.dateToString()); + } else { + serverInfo.setUpdateTime(DateUtils.dateToString()); + } + return serveService.saveOrUpdate(serverInfo) ? Result.success("成功") : Result.error("失败"); + } + + @ApiOperation("删除服务端") + @GetMapping("/deleteById") + public Result deleteById(@RequestParam String serverId) { + return serveService.deleteById(serverId); + } + + /** + * 断开并移除指定服务端连接 + */ + @ApiOperation("断开指定服务端连接") + @GetMapping("/disconnectServer") + public Result disconnectServer(@RequestParam String serverId) { + serveService.disconnectServer(serverId); + return Result.success("已移除服务端[" + serverId + "]的连接"); + } + + + /** + * 连接指定服务端 + */ + @ApiOperation("连接指定服务端") + @GetMapping("/connectServer") + public Result connectServer(@RequestParam String serverId) { + //查询服务端信息 + return serveService.connectOrDisconnect(serverId); + } + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/controller/SystemConfigController.java b/src/main/java/com/cbsd/universaltestsoftware_client/controller/SystemConfigController.java new file mode 100644 index 0000000..f0788a0 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/controller/SystemConfigController.java @@ -0,0 +1,52 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.cbsd.universaltestsoftware_client.dto.DataClearDto; +import com.cbsd.universaltestsoftware_client.service.SystemConfigService; +import com.cbsd.universaltestsoftware_client.util.Result; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@RestController +@Api(tags = "系统配置") +@RequestMapping("/api/config") +public class SystemConfigController { + + @Resource + private SystemConfigService systemConfigService; + + @ApiOperation("获取显示列列表") + @GetMapping("/getShowColumnList") + public Result> getShowColumnList(){ + return systemConfigService.getShowColumnList(); + } + + @ApiOperation("设置显示列列表") + @PostMapping("/setShowColumnList") + public Result setShowColumnList(@RequestBody List columnList){ + return systemConfigService.setShowColumnList(columnList); + } + + @ApiOperation("获取数据清理上限") + @GetMapping("/getDataClearLimit") + public Result getDataClearLimit() { + return Result.success(systemConfigService.getSystemConfig(2)); + } + + @ApiOperation("设置数据清理上限") + @PostMapping("/setDataClearLimit") + public Result setDataClearLimit(@RequestBody DataClearDto dto) { + if (dto == null || dto.getLimit() == null) { + return Result.error("数据上限不能为空"); + } + try { + systemConfigService.setSystemConfig(2, String.valueOf(dto.getLimit())); + } catch (Exception e) { + return Result.error("设置失败"); + } + return Result.success(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchChangeInstructParamDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchChangeInstructParamDto.java new file mode 100644 index 0000000..ae8e3b9 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchChangeInstructParamDto.java @@ -0,0 +1,40 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class BatchChangeInstructParamDto { + /** + * 用例id + */ + @ApiModelProperty(value = "用例id") + private String invocationId; + /** + * 指令id列表 + */ + @ApiModelProperty(value = "指令id列表") + private List instructIdList; + /** + * 状态 1.启用 0.禁用 + */ + @ApiModelProperty(value = "状态 1.启用 0.禁用") + private Integer status; + /** + * 断点 1.是 0.否 + */ + @ApiModelProperty(value = "断点 1.是 0.否") + private Integer isDebug; + /** + * 前置等待时间 + */ + @ApiModelProperty(value = "前置等待时间") + private Integer leadTime; + /** + * 后置等待时间 + */ + @ApiModelProperty(value = "后置等待时间") + private Integer tailTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchCopyBelowDTO.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchCopyBelowDTO.java new file mode 100644 index 0000000..b08d779 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchCopyBelowDTO.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class BatchCopyBelowDTO { + + @NotBlank(message = "invocationId不能为空") + private String invocationId; + @NotEmpty(message = "instructIds不能为空") + private List<@NotBlank String> instructIds; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchMoveBelowDTO.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchMoveBelowDTO.java new file mode 100644 index 0000000..eedcc83 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BatchMoveBelowDTO.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; +@Data +public class BatchMoveBelowDTO { + + @NotBlank(message = "invocationId不能为空") + private String invocationId; + + @NotNull(message = "移动的id,和sort的list") + private List instructs; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BoardTemperatureDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BoardTemperatureDto.java new file mode 100644 index 0000000..e8bb0ef --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BoardTemperatureDto.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class BoardTemperatureDto { + + @ApiModelProperty("服务端id") + private String serverId; + + @ApiModelProperty( "板卡id") + private String boardId; + + @ApiModelProperty( "板卡索引") + private String boardIndex; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BoardTemperatureServerDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BoardTemperatureServerDto.java new file mode 100644 index 0000000..2e97d8c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BoardTemperatureServerDto.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class BoardTemperatureServerDto { + + @ApiModelProperty("服务端id") + private String serverId; + + @ApiModelProperty( "板卡id") + private String boardId; + + @ApiModelProperty( "板卡索引") + private Integer boardIndex; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BootLogDeleteDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BootLogDeleteDto.java new file mode 100644 index 0000000..34e8b1f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BootLogDeleteDto.java @@ -0,0 +1,31 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class BootLogDeleteDto { + + @ApiModelProperty("服务端id") + private String serverId; + + @ApiModelProperty("主键ID,自增长") + private Long id; + + @ApiModelProperty("批量id") + private List listId; + + @ApiModelProperty("日志级别(如:DEBUG, INFO, WARN, ERROR)") + private String level; + + @ApiModelProperty(value = "开始时间") + private String startTime; + /** + * 结束时间 + */ + @ApiModelProperty(value = "结束时间") + private String endTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/BootLogPageDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BootLogPageDto.java new file mode 100644 index 0000000..146fb19 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/BootLogPageDto.java @@ -0,0 +1,29 @@ +package com.cbsd.universaltestsoftware_client.dto; + + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.BootLog; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class BootLogPageDto extends BasePage { + + @ApiModelProperty("日志级别(如:DEBUG, INFO, WARN, ERROR)") + private String level; + + + + @ApiModelProperty(value = "开始时间") + private String startTime; + /** + * 结束时间 + */ + @ApiModelProperty(value = "结束时间") + private String endTime; + + @ApiModelProperty("日志类型 1.软件日志 2.系统日志") + private Integer logType; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataDto.java new file mode 100644 index 0000000..f15c0a0 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataDto.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.ChannelData; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ChannelDataDto extends BasePage { + @ApiModelProperty("通道idList") + private List channelIdList; + + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataHistoryDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataHistoryDto.java new file mode 100644 index 0000000..75b5466 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataHistoryDto.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.dto; + + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.ChannelDataHistory; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ChannelDataHistoryDto extends BasePage { + + @ApiModelProperty("通道id") + private String channelId; + + @ApiModelProperty("服务端id") + @NotEmpty(message = "服务端id不能为空") + private String serverId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataQueryAll.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataQueryAll.java new file mode 100644 index 0000000..f27681b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataQueryAll.java @@ -0,0 +1,15 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class ChannelDataQueryAll { + + private List channelIds; + + private String startTime; + + private String endTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataStatisticsDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataStatisticsDto.java new file mode 100644 index 0000000..3a040a4 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDataStatisticsDto.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class ChannelDataStatisticsDto { + /** + * 数据看板id + */ + @ApiModelProperty("数据看板id") + private String dataBoardId; + /** + * 通道id + */ + @ApiModelProperty("通道id") + private String channelId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDto.java new file mode 100644 index 0000000..a7b1caf --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelDto.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class ChannelDto { + + @ApiModelProperty("通道id") + private String channelId; + + + @ApiModelProperty("通道名字") + private String channelName; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelPageDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelPageDto.java new file mode 100644 index 0000000..8300e17 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ChannelPageDto.java @@ -0,0 +1,28 @@ +package com.cbsd.universaltestsoftware_client.dto; + + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Channel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChannelPageDto extends BasePage { + + @ApiModelProperty("板卡id") + @NotBlank(message = "板卡id不可以为空") + private String boardId; + + @ApiModelProperty("服务id") + @NotBlank(message = "服务id不可以为空") + private String serverId; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/CreateGroupDTO.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/CreateGroupDTO.java new file mode 100644 index 0000000..de92762 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/CreateGroupDTO.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.List; + +@Data +public class CreateGroupDTO { + + @NotBlank + private String invocationId; + + @Size(min = 2, message = "至少选择 2 个节点") + private List<@NotBlank String> instructIds; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/DataClearDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/DataClearDto.java new file mode 100644 index 0000000..a6ffc47 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/DataClearDto.java @@ -0,0 +1,11 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class DataClearDto { + + @ApiModelProperty("数据上限") + private Integer limit; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/DeleteChannelDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/DeleteChannelDto.java new file mode 100644 index 0000000..4944fab --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/DeleteChannelDto.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class DeleteChannelDto { + + private List channelIds; + + @ApiModelProperty("开始时间") + private String startTime; + + @ApiModelProperty("结束时间") + private String endTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/DownloadDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/DownloadDto.java new file mode 100644 index 0000000..2ed841b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/DownloadDto.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Download; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class DownloadDto extends BasePage { + + @ApiModelProperty("1:生成中,2:已完成") + @TableField("state") + private Integer state; + + @ApiModelProperty("类型 1:解析数据 ,2:原数据") + private Integer type; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ExportDataBoardDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ExportDataBoardDto.java new file mode 100644 index 0000000..de2f68e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ExportDataBoardDto.java @@ -0,0 +1,46 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class ExportDataBoardDto { + + @ApiModelProperty("开始时间") + @NotEmpty(message = "开始时间不能为空") + private String startTime; + + @ApiModelProperty("结束时间") + @NotEmpty(message = "结束时间不能为空") + private String endTime; + + @ApiModelProperty("看板id") + @NotEmpty(message = "看板id不能为空") + private String dataBoardId; + + + @ApiModelProperty("参数") + private List param; + + @ApiModelProperty("导出类型1:解析,2原数据") +// @NotNull(message = "导出类型不能为空") + private Integer exportType; + + /** + * 解析数据类型 1.原码值 2.解析值 3.两者 + */ + @ApiModelProperty("解析数据类型 1.原码值 2.解析值 3.两者") + private Integer parseDataType; + + + @ApiModelProperty("协议ids") + private List protocolIds; + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/FileInfo.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/FileInfo.java new file mode 100644 index 0000000..1555dcc --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/FileInfo.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(description= "文件信息") +public class FileInfo { + @ApiModelProperty(value = "文件存储相对路径") + private String relativePath; + @ApiModelProperty(value = "文件原始名称") + private String originalFilename; + @ApiModelProperty(value = "网络文件读取路径") + private String nginxFileReadPath; + + @ApiModelProperty(value = "文件尺寸大小,单位kb") + private String fileSize; + @ApiModelProperty(value = "文件新名称") + private String newFileName; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InsertDataBoardDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InsertDataBoardDto.java new file mode 100644 index 0000000..afd337c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InsertDataBoardDto.java @@ -0,0 +1,35 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class InsertDataBoardDto { + + @ApiModelProperty("数据看板id") + private String dataBoardId; + + @ApiModelProperty("看板类型,0:表格,1:曲线") + @TableField("data_board_type") + private Integer dataBoardType; + + @ApiModelProperty("看板名字") + @TableField("data_board_name") + private String dataBoardName; + + @ApiModelProperty("0:遥测,1:数据回放") + @NotNull(message = "数据类型不能为空") + private Integer dataType; + + @ApiModelProperty("通道lsit") + private List channelList; + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructCalcDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructCalcDto.java new file mode 100644 index 0000000..75953d3 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructCalcDto.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class InstructCalcDto { + + private String data; + + private Integer calcMethod; + + private Integer byteCount; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructDeleteDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructDeleteDto.java new file mode 100644 index 0000000..a5ea05a --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructDeleteDto.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class InstructDeleteDto { + + private String invocationId; + + private List instructIds; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructDeleteFileDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructDeleteFileDto.java new file mode 100644 index 0000000..a1abf79 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructDeleteFileDto.java @@ -0,0 +1,14 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class InstructDeleteFileDto { + + private String instructId; + + /** + * 删除文件类型 1.预处理 2.分支条件 3.检查 4.指令数据 5.协议 + */ + private Integer deleteFileType; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructGroupDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructGroupDto.java new file mode 100644 index 0000000..db9c658 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructGroupDto.java @@ -0,0 +1,35 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class InstructGroupDto { + + @ApiModelProperty("指令id") + private String instructId; + + @ApiModelProperty("指令名字") + private String instructName; + + @ApiModelProperty("指令类型 1.常规指令 2.指令组") + private Integer instructType; + + @ApiModelProperty("用列id") + @NotBlank(message = "用列id不能为空") + private String invocationId; + + @ApiModelProperty("父级编码") + private String parentEncoding; + + @ApiModelProperty("排序") +// @NotNull(message = "排序不能为空") + private Integer sort; + + @ApiModelProperty("需要新增在他下方id") + private String inferiorId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructItem.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructItem.java new file mode 100644 index 0000000..7ac4159 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructItem.java @@ -0,0 +1,164 @@ +package com.cbsd.universaltestsoftware_client.dto; + + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.*; + +@Data +@NoArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(propOrder = { + "instructId", "instructType", "encoding", "parentEncoding", "parentName", + "instructName", "serverId", "ipAddress", "tcpPort", "instructText", + "channelId", "channelName", "invocationId", "invocationName","operationType", "extraData", + "boardTypeName", "boardIndex", "protocolId", "protocolName", "protocolContent", + "sort", "status", "leadTime", "tailTime", "hitCount", "lastHitTime", + "instructTag", "preProcessScript", "preProcessScriptName", "preProcessScriptParam", + "preProcessScriptFile", "preProcessScriptComment", "repeatCount", + "branchConditionScript", "branchConditionScriptName", "branchConditionScriptFile", + "branchConditionScriptParam", "branchConditionScriptComment", + "checkScript", "checkScriptName", "checkScriptFile", "checkScriptComment", + "instructDataFile", "jumpTarget", "breakPoint" +}) +public class InstructItem { + + @XmlElement(name = "InstructId") + private String instructId; + + @XmlElement(name = "InstructType") + private Integer instructType; + + @XmlElement(name = "Encoding") + private String encoding; + + @XmlElement(name = "ParentEncoding") + private String parentEncoding; + + @XmlElement(name = "ParentName") + private String parentName; + + @XmlElement(name = "InstructName") + private String instructName; + + @XmlElement(name = "ServerId") + private String serverId; + + @XmlElement(name = "IpAddress") + private String ipAddress; + + @XmlElement(name = "TcpPort") + private Integer tcpPort; + + @XmlElement(name = "InstructText") + private String instructText; + + @XmlElement(name = "ChannelId") + private String channelId; + + @XmlElement(name = "ChannelName") + private String channelName; + + @XmlElement(name = "InvocationId") + private String invocationId; + + @XmlElement(name = "InvocationName") + private String invocationName; + + @XmlElement(name = "OperationType") + private Integer operationType; + + @XmlElement(name = "ExtraData") + private String extraData; + + @XmlElement(name = "BoardTypeName") + private String boardTypeName; + + @XmlElement(name = "BoardIndex") + private String boardIndex; + + @XmlElement(name = "ProtocolId") + private String protocolId; + + @XmlElement(name = "ProtocolName") + private String protocolName; + + @XmlElement(name = "ProtocolContent") + private String protocolContent; + + @XmlElement(name = "Sort") + private Integer sort; + + @XmlElement(name = "Status") + private Integer status; + + @XmlElement(name = "LeadTime") + private Integer leadTime; + + @XmlElement(name = "TailTime") + private Integer tailTime; + + @XmlElement(name = "HitCount") + private Integer hitCount; + + @XmlElement(name = "LastHitTime") + private String lastHitTime; + + @XmlElement(name = "InstructTag") + private String instructTag; + + @XmlElement(name = "PreProcessScript") + private String preProcessScript; + + @XmlElement(name = "PreProcessScriptName") + private String preProcessScriptName; + + @XmlElement(name = "PreProcessScriptParam") + private String preProcessScriptParam; + + @XmlElement(name = "PreProcessScriptFile") + private String preProcessScriptFile; + + @XmlElement(name = "PreProcessScriptComment") + private String preProcessScriptComment; + + @XmlElement(name = "RepeatCount") + private Integer repeatCount; + + @XmlElement(name = "BranchConditionScript") + private String branchConditionScript; + + @XmlElement(name = "BranchConditionScriptName") + private String branchConditionScriptName; + + @XmlElement(name = "BranchConditionScriptFile") + private String branchConditionScriptFile; + + @XmlElement(name = "BranchConditionScriptParam") + private String branchConditionScriptParam; + + @XmlElement(name = "BranchConditionScriptComment") + private String branchConditionScriptComment; + + @XmlElement(name = "CheckScript") + private String checkScript; + + @XmlElement(name = "CheckScriptName") + private String checkScriptName; + + @XmlElement(name = "CheckScriptFile") + private String checkScriptFile; + + @XmlElement(name = "CheckScriptComment") + private String checkScriptComment; + + @XmlElement(name = "InstructDataFile") + private String instructDataFile; + + @XmlElement(name = "JumpTarget") + private String jumpTarget; + + @XmlElement(name = "BreakPoint") + private Integer breakPoint; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructPageDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructPageDto.java new file mode 100644 index 0000000..24d7f7e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructPageDto.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class InstructPageDto extends BasePage { + + @ApiModelProperty("用列id") + @NotBlank(message = "用列id不能为空") + private String invocationId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructXmlDTO.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructXmlDTO.java new file mode 100644 index 0000000..9c2eba2 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InstructXmlDTO.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.*; +import java.util.List; + +@Data +@NoArgsConstructor +@XmlRootElement(name = "Instructs") +@XmlAccessorType(XmlAccessType.FIELD) +public class InstructXmlDTO { + + @XmlElement(name = "Instruct") + private List instructs; + + public InstructXmlDTO(List instructs) { + this.instructs = instructs; + } +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InvocationPageDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InvocationPageDto.java new file mode 100644 index 0000000..a686960 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InvocationPageDto.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class InvocationPageDto extends BasePage { + + @ApiModelProperty("方案id") + @NotBlank(message = "方案id不能为空") + private String schemeId; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/InvocationSortDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InvocationSortDto.java new file mode 100644 index 0000000..d16eb65 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/InvocationSortDto.java @@ -0,0 +1,11 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class InvocationSortDto { + + private String schemeId; + private String invocationId; + private Integer sort; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageBoard.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageBoard.java new file mode 100644 index 0000000..ae58ca5 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageBoard.java @@ -0,0 +1,19 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Board; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PageBoard extends BasePage { + @ApiModelProperty("服务端id") + @NotEmpty(message = "服务端id不能为空") + private String serverId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageChannelDataDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageChannelDataDto.java new file mode 100644 index 0000000..a97e632 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageChannelDataDto.java @@ -0,0 +1,30 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@Data +public class PageChannelDataDto { + + @ApiModelProperty("开始时间") + private String startTime; + + @ApiModelProperty("结束时间") + private String endTime; + + @ApiModelProperty("条数") + @NotNull(message = "条数不能为空") + private Integer pageSize; + + @ApiModelProperty("数据看板id") + @NotEmpty(message = "数据看板id不能为空") + private String dataBoardId; + + private Integer want; + + private String lastTime; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageLogDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageLogDto.java new file mode 100644 index 0000000..291e708 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageLogDto.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +@Data +public class PageLogDto extends BasePage { + @ApiModelProperty("服务端id") + @NotEmpty(message = "服务端id不能为空") + private String serverId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageServerInfoDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageServerInfoDto.java new file mode 100644 index 0000000..38f01a7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/PageServerInfoDto.java @@ -0,0 +1,14 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class PageServerInfoDto extends BasePage { + + @ApiModelProperty("连接状态0:不连接,1要连接") + private Integer connectStatus; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolImport.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolImport.java new file mode 100644 index 0000000..bd45fa4 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolImport.java @@ -0,0 +1,295 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.alibaba.excel.annotation.ExcelProperty; + +public class ProtocolImport { + + public static class BasicInfo { + @ExcelProperty("readName") + private String readName= ""; + @ExcelProperty("frameHeadValue") + private String frameHeadValue= ""; + @ExcelProperty("frameTailValue") + private String frameTailValue= ""; + @ExcelProperty("checkStartSn") + private String checkStartSn= ""; + @ExcelProperty("checkEndSn") + private String checkEndSn= ""; + @ExcelProperty("verifyScript") + private String verifyScript= ""; + + public String getReadName() { + return readName; + } + + public void setReadName(String readName) { + this.readName = readName; + } + + public String getFrameHeadValue() { + return frameHeadValue; + } + + public void setFrameHeadValue(String frameHeadValue) { + this.frameHeadValue = frameHeadValue; + } + + public String getFrameTailValue() { + return frameTailValue; + } + + public void setFrameTailValue(String frameTailValue) { + this.frameTailValue = frameTailValue; + } + + public String getCheckStartSn() { + return checkStartSn; + } + + public void setCheckStartSn(String checkStartSn) { + this.checkStartSn = checkStartSn; + } + + public String getCheckEndSn() { + return checkEndSn; + } + + public void setCheckEndSn(String checkEndSn) { + this.checkEndSn = checkEndSn; + } + + public String getVerifyScript() { + return verifyScript; + } + + public void setVerifyScript(String verifyScript) { + this.verifyScript = verifyScript; + } + } + + public static class Filed { + @ExcelProperty("sn") + private String sn = ""; + @ExcelProperty("label") + private String label = ""; + @ExcelProperty("readName") + private String readName = ""; + @ExcelProperty("phyType") + private String phyType = ""; + @ExcelProperty("timeFormat") + private String timeFormat = ""; + @ExcelProperty("bitslen") + private String bitslen = ""; + @ExcelProperty("bitmask") + private String bitmask = ""; + @ExcelProperty("phyInnerScript") + private String phyInnerScript = ""; + @ExcelProperty("phyScript") + private String phyScript = ""; + @ExcelProperty("endian") + private String endian = ""; + @ExcelProperty("type") + private String type = ""; + @ExcelProperty("frame") + private String frame = ""; + @ExcelProperty("calcScript") + private String calcScript = ""; + @ExcelProperty("phyUnit") + private String phyUnit = ""; + @ExcelProperty("binStr") + private String binStr = ""; + @ExcelProperty("hex") + private String hex = ""; + @ExcelProperty("phy") + private String phy = ""; + @ExcelProperty("avg") + private String avg = ""; + @ExcelProperty("min") + private String min = ""; + @ExcelProperty("max") + private String max = ""; + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getReadName() { + return readName; + } + + public void setReadName(String readName) { + this.readName = readName; + } + + public String getPhyType() { + return phyType; + } + + public void setPhyType(String phyType) { + this.phyType = phyType; + } + + public String getTimeFormat() { + return timeFormat; + } + + public void setTimeFormat(String timeFormat) { + this.timeFormat = timeFormat; + } + + public String getBitslen() { + return bitslen; + } + + public void setBitslen(String bitslen) { + this.bitslen = bitslen; + } + + public String getBitmask() { + return bitmask; + } + + public void setBitmask(String bitmask) { + this.bitmask = bitmask; + } + + public String getPhyInnerScript() { + return phyInnerScript; + } + + public void setPhyInnerScript(String phyInnerScript) { + this.phyInnerScript = phyInnerScript; + } + + public String getPhyScript() { + return phyScript; + } + + public void setPhyScript(String phyScript) { + this.phyScript = phyScript; + } + + public String getEndian() { + return endian; + } + + public void setEndian(String endian) { + this.endian = endian; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFrame() { + return frame; + } + + public void setFrame(String frame) { + this.frame = frame; + } + + public String getCalcScript() { + return calcScript; + } + + public void setCalcScript(String calcScript) { + this.calcScript = calcScript; + } + + public String getPhyUnit() { + return phyUnit; + } + + public void setPhyUnit(String phyUnit) { + this.phyUnit = phyUnit; + } + + public String getBinStr() { + return binStr; + } + + public void setBinStr(String binStr) { + this.binStr = binStr; + } + + public String getHex() { + return hex; + } + + public void setHex(String hex) { + this.hex = hex; + } + + public String getPhy() { + return phy; + } + + public void setPhy(String phy) { + this.phy = phy; + } + + public String getAvg() { + return avg; + } + + public void setAvg(String avg) { + this.avg = avg; + } + + public String getMin() { + return min; + } + + public void setMin(String min) { + this.min = min; + } + + public String getMax() { + return max; + } + + public void setMax(String max) { + this.max = max; + } + } + + public static class VariableLength { + @ExcelProperty("value") + private String value= ""; + @ExcelProperty("length") + private String length= ""; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getLength() { + return length; + } + + public void setLength(String length) { + this.length = length; + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolNameListDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolNameListDto.java new file mode 100644 index 0000000..3525f5b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolNameListDto.java @@ -0,0 +1,15 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; +@Data +public class ProtocolNameListDto extends DataBoardChannel { + + @ApiModelProperty("协议参数list") + @TableField(exist = false) + private List parameterList; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolPageDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolPageDto.java new file mode 100644 index 0000000..34ca82c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ProtocolPageDto.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ProtocolPageDto extends BasePage { + + @ApiModelProperty("协议类型 1.解析 2.指令") + private Integer protocolType; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptContentDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptContentDto.java new file mode 100644 index 0000000..7a12a5f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptContentDto.java @@ -0,0 +1,14 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class ScriptContentDto { + + private String instructId; + + private String scriptType; + + private String scriptContent; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptDto.java new file mode 100644 index 0000000..1f4679e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptDto.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class ScriptDto { + + private String scriptType; + + private String scriptFileName; + + private String scriptName; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptFileContentDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptFileContentDto.java new file mode 100644 index 0000000..80a2bcb --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ScriptFileContentDto.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class ScriptFileContentDto { + + private String scriptType; + + private String filePath; + + private String content; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/SensorEquipmentDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SensorEquipmentDto.java new file mode 100644 index 0000000..3f70193 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SensorEquipmentDto.java @@ -0,0 +1,32 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +@Data +public class SensorEquipmentDto { + + /** + * 设备id + */ + private String equipmentId; + /** + * 设备名称 + */ + private String equipmentName; + /** + * 设备描述 + */ + private String equipmentDesc; + /** + * 设备参数 + */ + private String equipmentParam; + /** + * 通道id + */ + private String channelId; + /** + * 通道id + */ + private String channelAddress; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/SensorEquipmentPageDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SensorEquipmentPageDto.java new file mode 100644 index 0000000..74a702e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SensorEquipmentPageDto.java @@ -0,0 +1,15 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.entity.SensorEquipment; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class SensorEquipmentPageDto extends Page { + + private String equipmentName; + + private String channelId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/ServerPageChannelDataDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ServerPageChannelDataDto.java new file mode 100644 index 0000000..4881f35 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/ServerPageChannelDataDto.java @@ -0,0 +1,27 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; +@Data +public class ServerPageChannelDataDto { + + @ApiModelProperty("开始时间") + private Long startTime; + + @ApiModelProperty("结束时间") + private Long endTime; + + @ApiModelProperty("条数") + @NotNull(message = "条数不能为空") + private Integer pageSize; + + @ApiModelProperty("数据看板id") + @NotEmpty(message = "数据看板id不能为空") + private String dataBoardId; + + private List channelIds; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/SortDto.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SortDto.java new file mode 100644 index 0000000..ad4c8f5 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SortDto.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class SortDto { + + @NotBlank(message = "instructId不能为空") + private String instructId; + + @NotNull(message = "sort不能为空") + private Integer sort; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/dto/SubmitAndCompile.java b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SubmitAndCompile.java new file mode 100644 index 0000000..7532fdc --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/dto/SubmitAndCompile.java @@ -0,0 +1,25 @@ +package com.cbsd.universaltestsoftware_client.dto; + + + +public class SubmitAndCompile { + + private String className; + private String code; + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Board.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Board.java new file mode 100644 index 0000000..a2068be --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Board.java @@ -0,0 +1,68 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + *

+ * 板卡 + *

+ * + * @author admin + * @since 2025-08-26 + */ +@Getter +@Setter +/*@TableName("board")*/ +@ApiModel(value = "Board对象", description = "板卡") +public class Board { + + @ApiModelProperty("板卡id") + private String boardId; + + @ApiModelProperty("板卡类型主键Id") + @NotBlank(message = "板卡类型不能为空") + private String boardTypeId; + @NotBlank(message = "服务id不能为空") + private String serverId; + + @ApiModelProperty("板卡名字") + @NotBlank(message = "板卡名字不能为空") + private String boardName; + + @ApiModelProperty("板卡编号") + @NotBlank(message = "板卡编号不能为空") + private String boardIndex; + + @ApiModelProperty("状态,0不在线,1在线") + private Integer onlineState; + + @ApiModelProperty("通道数量") + private Integer channelNum; + + @ApiModelProperty("创建时间") + private String createTime; + + @ApiModelProperty("修改时间") + private String updateTime; + + @ApiModelProperty("是否删除0否1是") + private String isDelete; + + @ApiModelProperty("板卡类型名称 用户看") + @TableField(exist = false) + private String boardTypeName; + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/BootLog.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/BootLog.java new file mode 100644 index 0000000..7f68322 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/BootLog.java @@ -0,0 +1,65 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *

+ * 系统启动日志表,记录应用程序运行过程中的日志信息 + *

+ * + * @author wt + * @since 2025-09-03 + */ +@Getter +@Setter +@TableName("boot_log") +@ApiModel(value = "BootLog对象", description = "系统启动日志表,记录应用程序运行过程中的日志信息") +public class BootLog implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("主键ID,自增长") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty("事件ID,用于唯一标识一个日志事件") + private String eventId; + + @ApiModelProperty("事件发生时间") + @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime eventDate; + + @ApiModelProperty("产生日志的线程名称") + private String thread; + + @ApiModelProperty("产生日志的类名") + private String logClass; + + @ApiModelProperty("产生日志的方法/函数名") + private String method; + + @ApiModelProperty("日志消息内容") + private String message; + + @ApiModelProperty("异常堆栈信息") + private String exception; + + @ApiModelProperty("日志级别(如:DEBUG, INFO, WARN, ERROR)") + private String level; + + @ApiModelProperty("日志记录时间") + @JsonFormat(shape = JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime time; + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Channel.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Channel.java new file mode 100644 index 0000000..b34625e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Channel.java @@ -0,0 +1,65 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class Channel { + @ApiModelProperty("通道id") + @TableId(value = "channel_id", type = IdType.ASSIGN_UUID) + private String channelId; + + @ApiModelProperty("通道类型id") + private String channelTypeId; + + @ApiModelProperty("服务id") + @NotBlank(message = "服务id不能为空") + private String serverId; + + @ApiModelProperty("通道路径") + private String channelPath; + + @ApiModelProperty("板卡id") + private String boardId; + + @ApiModelProperty("解析数量") + private int parsingQuantity; + + @ApiModelProperty("字节流量") + private int byteCount; + + @ApiModelProperty("通道名称") + private String channelName; + + @ApiModelProperty("创建时间") + @TableField(fill = FieldFill.INSERT) + private String createTime; + + @ApiModelProperty("更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateTime; + + @ApiModelProperty("是否删除0否1是") + private String isDelete; + + @ApiModelProperty("读写通道类型") + private String rwChannelType; + + @ApiModelProperty("是否允许修改通道类型 ") + private Integer isUpType; + + @ApiModelProperty("通道类型 程序使用") + @TableField(exist = false) + private String channelType; + + @ApiModelProperty("通道类型 用户看") + @TableField(exist = false) + private String channelTypeName; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelData.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelData.java new file mode 100644 index 0000000..b168cad --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelData.java @@ -0,0 +1,46 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 解析的数据 + * @TableName channel_data + */ +@TableName(value ="channel_data") +@Data +public class ChannelData { + /** + * 解析数据id + */ + @TableId + private String dataId; + + /** + * 通道id + */ + private String channelId; + + /** + * 数据内容 + */ + private String data; + + /** + * 协议id + */ + private String protocolId; + + /** + * 协议内容 + */ + private String protocolContent; + + /** + * 数据时间 + */ + private String time; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataHistory.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataHistory.java new file mode 100644 index 0000000..bdc4c78 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataHistory.java @@ -0,0 +1,61 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + *

+ * 通道历史数据 + *

+ * + * @author wt + * @since 2025-09-28 + */ +@Getter +@Setter +@TableName("channel_data_history") +@ApiModel(value = "ChannelDataHistory对象", description = "通道历史数据") +public class ChannelDataHistory implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("通道历史数据id") + @TableId(value = "channel_data_history_id", type = IdType.ASSIGN_UUID) + private String channelDataHistoryId; + + @ApiModelProperty("通道id") + private String channelId; + + @ApiModelProperty("创建时间") + private String createTime; + + + @ApiModelProperty("是否删除0否1是") + private String isDelete; + + @ApiModelProperty("数据信息") + private byte[] content; + + @ApiModelProperty("数据字符串") + @TableField(exist = false) + private String contentString; + + @ApiModelProperty("时间戳") + private Long timestamp; + + /** + * 额外数据 + */ + @ApiModelProperty("额外数据") + private String extraData; + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataHit.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataHit.java new file mode 100644 index 0000000..1425f60 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataHit.java @@ -0,0 +1,41 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 通道协议命中次数 + * @TableName channel_data_hit + */ +@TableName(value ="channel_data_hit") +@Data +public class ChannelDataHit { + /** + * 命中id + */ + @TableId + private String hitId; + + /** + * 通道id + */ + private String channelId; + + /** + * 协议id + */ + private String protocolId; + + /** + * 命中次数 + */ + private Integer hitCount; + + /** + * 命中更新时间 + */ + private String updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataTraffic.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataTraffic.java new file mode 100644 index 0000000..26cf18e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ChannelDataTraffic.java @@ -0,0 +1,36 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 通道流量统计 + * @TableName channel_data_traffic + */ +@TableName(value ="channel_data_traffic") +@Data +public class ChannelDataTraffic { + /** + * 数据流量id + */ + @TableId + private String dataTrafficId; + + /** + * 通道id + */ + private String channelId; + + /** + * 字节数 + */ + private Integer byteCount; + + /** + * 更新时间 + */ + private String updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/DataBoard.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/DataBoard.java new file mode 100644 index 0000000..097f50c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/DataBoard.java @@ -0,0 +1,50 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + *

+ * 数据看板 + *

+ * + * @author admin + * @since 2025-09-10 + */ +@Getter +@Setter +@TableName("data_board") +@ApiModel(value = "DataBoard对象", description = "数据看板") +public class DataBoard { + + @ApiModelProperty("数据看板id") + @TableId(value = "data_board_id", type = IdType.ASSIGN_UUID) + private String dataBoardId; + + @ApiModelProperty("看板类型,0:表格,1:曲线") + @TableField("data_board_type") + private Integer dataBoardType; + + @ApiModelProperty("看板名字") + @TableField("data_board_name") + private String dataBoardName; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + + @ApiModelProperty("0:遥测,1:数据回放") + private Integer dataType; + + @ApiModelProperty("通道list") + @TableField(exist = false) + private List dataBoardChannelList; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/DataBoardChannel.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/DataBoardChannel.java new file mode 100644 index 0000000..e414b38 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/DataBoardChannel.java @@ -0,0 +1,75 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.cbsd.universaltestsoftware_client.dto.ProtocolNameListDto; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + *

+ * + *

+ * + * @author admin + * @since 2025-09-10 + */ +@Getter +@Setter +@TableName("data_board_channel") +@ApiModel(value = "DataBoardChannel对象", description = "") +public class DataBoardChannel { + + @ApiModelProperty("数据看板通道id") + @TableId(value = "data_board_channel_id", type = IdType.ASSIGN_UUID) + private String dataBoardChannelId; + + @ApiModelProperty("数据看板id") + @TableField("data_board_id") + private String dataBoardId; + + @ApiModelProperty("通道Id") + @TableField("channel_id") + private String channelId; + + @ApiModelProperty("通道名字") + @TableField("channel_name") + private String channelName; + + @ApiModelProperty("参数名字") + @TableField("parameter") + private String parameter; + + @ApiModelProperty("协议id") + private String protocolId; + + @ApiModelProperty("协议名字") + private String protocolName; + + @ApiModelProperty("索引") + private Integer sn; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + +// @ApiModelProperty("协议参数list") +// @TableField(exist = false) +// private List parameterList; + + @ApiModelProperty("协议list") + @TableField(exist = false) + private List protocolNameList; + + @ApiModelProperty("是否左轴") + private Integer isLeftYaxis; + + @ApiModelProperty("颜色") + private String legendColor; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Download.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Download.java new file mode 100644 index 0000000..f1e8468 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Download.java @@ -0,0 +1,59 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * + *

+ * + * @author admin + * @since 2026-02-03 + */ +@Getter +@Setter +@TableName("download") +@ApiModel(value = "Download对象", description = "") +public class Download { + + @ApiModelProperty("下载id") + @TableId(value = "download_id", type = IdType.ASSIGN_UUID) + private String downloadId; + + @ApiModelProperty("开始时间") + @TableField("start_time") + private String startTime; + + @ApiModelProperty("结束时间") + @TableField("end_time") + private String endTime; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + + @ApiModelProperty("通道名字") + @TableField("channel_name") + private String channelName; + + @ApiModelProperty("协议名字") + @TableField("protocol_name") + private String protocolName; + + @ApiModelProperty("1:生成中,2:已完成") + @TableField("state") + private Integer state; + + @ApiModelProperty("下载地址") + @TableField("download_url") + private String downloadUrl; + @ApiModelProperty("类型 1:解析数据 ,2:原数据") + private Integer type; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Instruct.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Instruct.java new file mode 100644 index 0000000..2a66322 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Instruct.java @@ -0,0 +1,190 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * + *

+ * + * @author admin + * @since 2025-09-01 + */ +@Getter +@Setter +@TableName("instruct") +@ApiModel(value = "Instruct对象", description = "") +public class Instruct { + + @ApiModelProperty("指令id") + @TableId(value = "instruct_id", type = IdType.ASSIGN_UUID) + private String instructId; + + + @ApiModelProperty("指令类型 1.常规指令 2.指令组") + @TableField("instruct_type") + private Integer instructType; + + @ApiModelProperty("指令名字") + @TableField("instruct_name") + private String instructName; + + @ApiModelProperty("用列id") + @TableField("invocation_id") + private String invocationId; + + @ApiModelProperty("服务id") + @TableField("server_id") + private String serverId; + + @ApiModelProperty("ip地址") + private String ipAddress; + + @ApiModelProperty("服务端tcp端口") + private Integer tcpPort; + + @ApiModelProperty("指令数据") + @TableField("instruct_text") + private String instructText; + + @ApiModelProperty("指令数据列表") + private String instructTextData; + + @ApiModelProperty("指令寄存器方法名称") + @TableField("instruct_function_name") + private String instructFunctionName; + + @ApiModelProperty("通道id") + @TableField("channel_id") + private String channelId; + + @ApiModelProperty("协议id") + private String protocolId; + + @ApiModelProperty("指令类型 0.数据 1.寄存器") + private Integer operationType; + + @ApiModelProperty("额外数据") + private String extraData; + + @ApiModelProperty("板卡编号") + private String boardIndex; + + @ApiModelProperty("板卡类型名称") + private String boardTypeName; + + @ApiModelProperty("状态0:禁用,1:启用") + private Integer status; + + @ApiModelProperty("父级编码") + private String parentEncoding; + + @ApiModelProperty("父级名字") + private String parentName; + + @ApiModelProperty("排序") + @NotNull(message = "排序不能为空") + private Integer sort; + + @ApiModelProperty("通道名字") + private String channelName; + @ApiModelProperty("前置时间") + private Integer leadTime; + + @ApiModelProperty("后置时间") + private Integer tailTime; + + @ApiModelProperty("命中次数") + private Integer hitCount; + + @ApiModelProperty("最后命中时间") + private String lastHitTime; + + @ApiModelProperty("指令标识") + private String instructTag; + + @ApiModelProperty("预处理脚本") + private String preProcessScript; + + @ApiModelProperty("预处理脚本名称") + private String preProcessScriptName; + + @ApiModelProperty("预处理脚本文件") + private String preProcessScriptFile; + + @ApiModelProperty("预处理脚本参数") + private String preProcessScriptParam; + + @ApiModelProperty("预处理脚本注释") + private String preProcessScriptComment; + + @ApiModelProperty("重复次数") + private Integer repeatCount; + + @ApiModelProperty("分支条件脚本") + private String branchConditionScript; + + @ApiModelProperty("分支条件脚本名称") + private String branchConditionScriptName; + + @ApiModelProperty("分支条件脚本文件") + private String branchConditionScriptFile; + + @ApiModelProperty("分支条件脚本参数") + private String branchConditionScriptParam; + + @ApiModelProperty("分支条件脚本注释") + private String branchConditionScriptComment; + + @ApiModelProperty("检查脚本") + private String checkScript; + + @ApiModelProperty("检查脚本名称") + private String checkScriptName; + + @ApiModelProperty("检查脚本文件") + private String checkScriptFile; + + @ApiModelProperty("检查脚本注释") + private String checkScriptComment; + + @ApiModelProperty("指令数据文件") + private String instructDataFile; + + @ApiModelProperty("跳转目标") + private String jumpTarget; + + @ApiModelProperty("断点 1.是 0.否") + private Integer breakPoint; + + @ApiModelProperty("编码") + private String encoding; + + @TableField(exist = false) + private List children=new ArrayList<>(); + + @ApiModelProperty("协议名称") + private String protocolName; + + @ApiModelProperty("数据拆分长度,单位:字节") + private Integer dataSplittingLength; + + /** + * 是否需要运行 + */ + @TableField(exist = false) + private boolean needRun; + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Invocation.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Invocation.java new file mode 100644 index 0000000..a2aad49 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Invocation.java @@ -0,0 +1,48 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 用例 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@Getter +@Setter +@TableName("invocation") +@ApiModel(value = "Invocation对象", description = "用例") +public class Invocation { + + @ApiModelProperty("调用用例id") + @TableId(value = "invocation_id", type = IdType.ASSIGN_UUID) + private String invocationId; + + @ApiModelProperty("用例名字") + @TableField("invocation_name") + private String invocationName; + + @ApiModelProperty("用例序号") + private Integer sort; + + @ApiModelProperty("方案id") + @TableField("scheme_id") + private String schemeId; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + + @ApiModelProperty("修改时间") + @TableField("update_time") + private String updateTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ParseData.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ParseData.java new file mode 100644 index 0000000..b8af574 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ParseData.java @@ -0,0 +1,36 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 解析的数据 + * @TableName parse_data + */ +@TableName(value ="parse_data") +@Data +public class ParseData { + /** + * 解析数据id + */ + @TableId + private String dataId; + + /** + * 通道id + */ + private String channelId; + + /** + * 数据内容 + */ + private String data; + + /** + * 数据时间 + */ + private String time; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Parser.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Parser.java new file mode 100644 index 0000000..8bf02ac --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Parser.java @@ -0,0 +1,61 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 解析器 + * @TableName parser + */ +@TableName(value ="parser") +@Data +public class Parser { + /** + * 解析器id + */ + @TableId(value = "parser_id", type = IdType.ASSIGN_UUID) + private String parserId; + + /** + * 解析器名称 + */ + @ApiModelProperty("解析器名称") + private String parserName; + + /** + * 解析器顺序 + */ + @ApiModelProperty("解析器顺序") + private Integer parserSort; + + /** + * 通道id + */ + @ApiModelProperty("通道id") + private String channelId; + + /** + * 是否删除 1.是 0.否 + */ + @ApiModelProperty("是否删除 1.是 0.否") + private Integer isDelete; + + /** + * 创建时间 + */ + @ApiModelProperty("创建时间") + private String createTime; + + /** + * 协议个数 + */ + @TableField(exist = false) + @ApiModelProperty("协议个数") + private Long protocolCount; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ParserProtocol.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ParserProtocol.java new file mode 100644 index 0000000..afe308b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ParserProtocol.java @@ -0,0 +1,48 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 解析器协议 + * @TableName parser_protocol + */ +@TableName(value ="parser_protocol") +@Data +public class ParserProtocol { + /** + * 解析器协议id + */ + @TableId(value = "parser_protocol_id",type = IdType.ASSIGN_UUID) + private String parserProtocolId; + + /** + * 解析器id + */ + @ApiModelProperty("解析器id") + @NotBlank(message = "解析器id不能为空") + private String parserId; + + /** + * 协议id + */ + @ApiModelProperty("协议id") + @NotBlank(message = "协议id不能为空") + private String protocolId; + + @TableField(exist = false) + @ApiModelProperty("协议名字") + private String protocolName; + + /** + * 创建时间 + */ + @ApiModelProperty("创建时间") + private String createTime; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Protocol.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Protocol.java new file mode 100644 index 0000000..252cc6b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Protocol.java @@ -0,0 +1,68 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +/** + *

+ * 协议 + *

+ * + * @author admin + * @since 2025-09-02 + */ +@Getter +@Setter +@TableName("protocol") +@ApiModel(value = "Protocol对象", description = "协议") +public class Protocol { + + @ApiModelProperty("协议id") + @TableId(value = "protocol_id", type = IdType.ASSIGN_ID) + private String protocolId; + + @ApiModelProperty("协议名字") + @TableField("protocol_name") + private String protocolName; + + @ApiModelProperty("协议内容") + @TableField("protocol_content") + private String protocolContent; + + @ApiModelProperty("协议类型 1.解析 2.指令") + @TableField("protocol_type") + private Integer protocolType; + + @ApiModelProperty("协议标签") + @TableField("protocol_tag") + private String protocolTag; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + + @ApiModelProperty("修改时间") + @TableField("update_time") + private String updateTime; + + /** + * 协议xml + */ + @TableField(exist = false) + private ProtocolXml protocolXml; + /** + * 协议字段类型map + */ + @TableField(exist = false) + private Map filedMap; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/Scheme.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Scheme.java new file mode 100644 index 0000000..5fddcba --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/Scheme.java @@ -0,0 +1,41 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 方案 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@Getter +@Setter +@TableName("scheme") +@ApiModel(value = "Scheme对象", description = "方案") +public class Scheme { + + @ApiModelProperty("方案id") + @TableId(value = "scheme_id", type = IdType.ASSIGN_UUID) + private String schemeId; + + @ApiModelProperty("方案名字") + @TableField("scheme_name") + private String schemeName; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + + @ApiModelProperty("修改时间") + @TableField("update_time") + private String updateTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/SensorEquipment.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/SensorEquipment.java new file mode 100644 index 0000000..ebc8d5c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/SensorEquipment.java @@ -0,0 +1,39 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@TableName(value ="sensor_equipment") +@Data +public class SensorEquipment { + /** + * 设备id + */ + @TableId + private String equipmentId; + /** + * 设备名称 + */ + private String equipmentName; + /** + * 设备描述 + */ + private String equipmentDesc; + /** + * 设备参数 + */ + private String equipmentParam; + /** + * 通道id + */ + private String channelId; + /** + * 通道id + */ + private String channelAddress; + /** + * 创建时间 + */ + private String createTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/ServerInfo.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ServerInfo.java new file mode 100644 index 0000000..a6ee92a --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/ServerInfo.java @@ -0,0 +1,76 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + *

+ * 服务端 + *

+ * + * @author admin + * @since 2025-08-26 + */ +@Getter +@Setter +@TableName("server") +@ApiModel(value = "Server对象", description = "服务端") +public class ServerInfo { + + @ApiModelProperty("服务端id") + @TableId(value = "server_id", type = IdType.ASSIGN_UUID) + private String serverId; + + @ApiModelProperty("服务端名字") + @TableField("server_name") + @NotBlank(message = "服务端名字不能为空") + private String serverName; + + @ApiModelProperty("ip地址") + @TableField("ip_address") + @NotBlank(message = "ip地址不能为空") + private String ipAddress; + + @ApiModelProperty("服务端tcp端口") + + private Integer tcpPort; + + @ApiModelProperty("服务端http端口") + @NotNull(message = "服务端http端口不能为空") + private Integer httpPort; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private String createTime; + + @ApiModelProperty("修改时间") + @TableField("update_time") + private String updateTime; + + @ApiModelProperty("是否删除0否1是") + @TableField("is_delete") + private String isDelete; + @ApiModelProperty("在线状态0:不在线,1在线") + @TableField(exist = false) + private Integer status; + + @ApiModelProperty("连接状态0:不连接,1要连接") + private Integer connectStatus; + + @ApiModelProperty("重连初始延迟") + private int reconnectInitialDelay = 1000; + @ApiModelProperty("重连最大延迟") + private int reconnectMaxDelay = 10000; + @ApiModelProperty("重连延迟乘数") + private double reconnectMultiplier = 1.5; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/entity/SystemConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/entity/SystemConfig.java new file mode 100644 index 0000000..58f838f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/entity/SystemConfig.java @@ -0,0 +1,26 @@ +package com.cbsd.universaltestsoftware_client.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 系统配置(仅类型和内容) + * @TableName system_config + */ +@TableName(value ="system_config") +@Data +public class SystemConfig { + /** + * 类型: 1.指令显示列 + */ + @TableId + private Integer type; + + /** + * 内容 + */ + private String content; +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/exception/BaseException.java b/src/main/java/com/cbsd/universaltestsoftware_client/exception/BaseException.java new file mode 100644 index 0000000..b519c33 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/exception/BaseException.java @@ -0,0 +1,26 @@ +package com.cbsd.universaltestsoftware_client.exception; + +import com.cbsd.universaltestsoftware_client.util.ResultCode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper=false) +@ToString +public class BaseException extends RuntimeException { + + private static final long serialVersionUID = 2895464961740660192L; + + private int code= ResultCode.SYSTEM_ERROR; + + public BaseException(String message) { + super(message); + } + + public BaseException(int code, String message) { + super(message); + this.code = code; + } + +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/exception/CustomizeException.java b/src/main/java/com/cbsd/universaltestsoftware_client/exception/CustomizeException.java new file mode 100644 index 0000000..3a95981 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/exception/CustomizeException.java @@ -0,0 +1,28 @@ +package com.cbsd.universaltestsoftware_client.exception; + + +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper=false) +public class CustomizeException extends RuntimeException{ + + /** + * + */ + private static final long serialVersionUID = 1L; + private String code; + + public CustomizeException(String message) { + super(message); + } + + public CustomizeException(String code, String message) { + super(message); + this.code = code; + } + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/exception/DbInsertOrUpdateErrorExcetpion.java b/src/main/java/com/cbsd/universaltestsoftware_client/exception/DbInsertOrUpdateErrorExcetpion.java new file mode 100644 index 0000000..371d439 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/exception/DbInsertOrUpdateErrorExcetpion.java @@ -0,0 +1,10 @@ +package com.cbsd.universaltestsoftware_client.exception; + +/** + * 插入或者修改数据库错误异常 + */ +public class DbInsertOrUpdateErrorExcetpion extends Exception{ + public DbInsertOrUpdateErrorExcetpion(String msg){ + super(msg); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/exception/GlobalExceptionHandler.java b/src/main/java/com/cbsd/universaltestsoftware_client/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0f98739 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/exception/GlobalExceptionHandler.java @@ -0,0 +1,97 @@ +package com.cbsd.universaltestsoftware_client.exception; + + +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.ResultCode; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.log4j.Log4j2; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.ConstraintViolationException; + +import java.util.List; + +@Log4j2 +@ControllerAdvice +public class GlobalExceptionHandler { + + @ResponseBody + @ExceptionHandler({Exception.class}) + public Result handleException(HttpServletRequest request, HttpServletResponse response, Exception e) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json; charset=utf-8"); + log.info("程序发生全局异常", e); + int code = ResultCode.SYSTEM_ERROR; + String message = ""; + if (e instanceof JsonProcessingException) { + message = "对象转json出错"; + } else if (e instanceof ConstraintViolationException) { + ConstraintViolationException constraintViolationException = (ConstraintViolationException) e; + code = ResultCode.SYSTEM_ERROR; + //获得第一个点的位置 + String str = constraintViolationException.getMessage(); + int index=str.indexOf(":"); + if (index!=-1) { + System.out.println(index); + //根据第二个点的位置,截取 字符串。得到结果 result + str=str.substring(index+1); + + } + message = str.trim(); + } else if (e instanceof BindException) { + BindException bindException = (BindException) e; + BindingResult bindingResult = bindException.getBindingResult(); + List objectErrorList = bindingResult.getAllErrors(); + if (objectErrorList != null && objectErrorList.size() > 0) { + for (ObjectError objectError : objectErrorList) { + message = objectError.getDefaultMessage(); + } + } + code = ResultCode.SYSTEM_ERROR; + } else if (e instanceof MethodArgumentNotValidException) { + MethodArgumentNotValidException me = (MethodArgumentNotValidException) e; + message = me.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + code = ResultCode.SYSTEM_ERROR; + } else if (e instanceof DuplicateKeyException) { +// DuplicateKeyException ex = (DuplicateKeyException) e; +// String msg = ex.getMessage(); +// msg.indexOf("entry") +// Duplicate entry '时尚' for key 'excel_name' + message = "不允许插入重复数据"; + } else if (e instanceof BaseException) { + BaseException be = (BaseException) e; + code = be.getCode(); + message = be.getMessage(); + } else if (e instanceof IllegalArgumentException) { + message = "参数不合法"; + } else if (e instanceof HttpRequestMethodNotSupportedException) { + message = "接口请求不规范,请检查接口请求"; + }else if (e instanceof HttpMessageNotReadableException){ + message = "接口请求不规范,参数格式错误"; +// } else if (e instanceof UnauthorizedException || e instanceof AuthorizationException) { +// message = "权限不足,请联系管理员。"; + } else if (e instanceof RuntimeException){ + message = e.getMessage(); + } else if( e instanceof NullPointerException){ + message = "参数不能为空"; + } else { + message = e.getMessage(); + } + return handleErrorInfo(code, message); + } + + private Result handleErrorInfo(int code, String message) { + return Result.error(code, message); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/BootLogMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/BootLogMapper.java new file mode 100644 index 0000000..8adb76e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/BootLogMapper.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cbsd.universaltestsoftware_client.entity.BootLog; + +/** + *

+ * Mapper 接口 + *

+ * + * @author wt + * @since 2025-09-03 + */ +public interface BootLogMapper extends BaseMapper { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataHitMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataHitMapper.java new file mode 100644 index 0000000..0dd025b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataHitMapper.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.ChannelDataHit; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author SanJie +* @description 针对表【channel_data_hit(通道协议命中次数)】的数据库操作Mapper +* @createDate 2026-01-23 16:24:09 +* @Entity com.cbsd.universaltestsoftware_client.entity.ChannelDataHit +*/ +public interface ChannelDataHitMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataMapper.java new file mode 100644 index 0000000..b76cc77 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataMapper.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.ChannelData; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author SanJie +* @description 针对表【channel_data(解析的数据)】的数据库操作Mapper +* @createDate 2025-09-09 09:53:44 +* @Entity com.cbsd.universaltestsoftware_client.entity.ChannelData +*/ +public interface ChannelDataMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataTrafficMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataTrafficMapper.java new file mode 100644 index 0000000..a60034a --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ChannelDataTrafficMapper.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.ChannelDataTraffic; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author SanJie +* @description 针对表【channel_data_traffic(通道流量统计)】的数据库操作Mapper +* @createDate 2026-01-23 10:07:19 +* @Entity com.cbsd.universaltestsoftware_client.entity.ChannelDataTraffic +*/ +public interface ChannelDataTrafficMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DataBoardChannelMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DataBoardChannelMapper.java new file mode 100644 index 0000000..6b7d9da --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DataBoardChannelMapper.java @@ -0,0 +1,20 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.config.IBaseMapper; +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author admin + * @since 2025-09-10 + */ +public interface DataBoardChannelMapper extends IBaseMapper { + + List getChannelList(String dataBoardId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DataBoardMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DataBoardMapper.java new file mode 100644 index 0000000..56baf31 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DataBoardMapper.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.DataBoard; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 数据看板 Mapper 接口 + *

+ * + * @author admin + * @since 2025-09-10 + */ +public interface DataBoardMapper extends BaseMapper { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DownloadMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DownloadMapper.java new file mode 100644 index 0000000..9cdecd6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/DownloadMapper.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.Download; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author admin + * @since 2026-02-03 + */ +public interface DownloadMapper extends BaseMapper { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/InstructMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/InstructMapper.java new file mode 100644 index 0000000..e565c66 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/InstructMapper.java @@ -0,0 +1,70 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.config.IBaseMapper; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author admin + * @since 2025-09-01 + */ +public interface InstructMapper extends IBaseMapper { + + + /** + * 查找当前节点的前一个兄弟节点(同父节点下,sort < 当前,取最大) + */ + Instruct findPrevSibling(@Param("sort") Integer sort, @Param("parentEncoding") String parentEncoding, @Param("invocationId") String invocationId); + + /** + * 查找当前节点的下一个兄弟节点(同父节点下,sort > 当前,取最小) + */ + Instruct findNextSibling(@Param("sort") Integer sort, @Param("parentEncoding") String parentEncoding, @Param("invocationId") String invocationId); + + /** + * 更新指令(只更新 sort 字段也可单独抽离) + */ + void updateSort(Instruct instruct); + + + /** + * 按 invocationId 全局重排 sort(1,2,3…) + */ + void reorderGlobalSortContinuous(@Param("invocationId") String invocationId); + + + // 根据 ids + invocationId 查询 + List selectByIdsAndInvocation(@Param("ids") List ids, + @Param("invocationId") String invocationId); + + // 根据 parentEncoding 精确查询直接子节点(不含孙) + List listByParentEncodingAndInvocation(@Param("invocationId") String invocationId, + @Param("parentCode") String parentCode); + + + void setSortZeroByIds(@Param("ids") List ids); + + + void shiftSortAfter(@Param("invocationId") String invocationId, @Param("sort") Integer sort,String instructId); + + /** + * 区间-1 + * @param invocationId + * @param + * @param instructId + */ + void minusSort(@Param("invocationId") String invocationId, @Param("oldSort") Integer oldSort,@Param("newSort") Integer newSort,@Param("instructId") String instructId); + + /** + * 区间+1 + */ + void plusSort(@Param("invocationId") String invocationId, @Param("oldSort") Integer oldSort,@Param("newSort") Integer newSort,@Param("instructId") String instructId); + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/InvocationMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/InvocationMapper.java new file mode 100644 index 0000000..2dad0c1 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/InvocationMapper.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 用列 Mapper 接口 + *

+ * + * @author admin + * @since 2025-09-01 + */ +public interface InvocationMapper extends BaseMapper { + + void subtractSort(String schemeId, int newSort, int oldSort); + + void addSort(String schemeId, int newSort, int oldSort); + + void unificationSubtractSort(String schemeId, int startSort); + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ParserMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ParserMapper.java new file mode 100644 index 0000000..21bb8bc --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ParserMapper.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.Parser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author SanJie +* @description 针对表【parser(解析器)】的数据库操作Mapper +* @createDate 2025-09-04 15:55:48 +* @Entity com.cbsd.universaltestsoftware_client.entity.Parser +*/ +public interface ParserMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ParserProtocolMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ParserProtocolMapper.java new file mode 100644 index 0000000..81b7fe7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ParserProtocolMapper.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.ParserProtocol; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author SanJie +* @description 针对表【parser_protocol(解析器协议)】的数据库操作Mapper +* @createDate 2025-09-04 15:56:52 +* @Entity com.cbsd.universaltestsoftware_client.entity.ParserProtocol +*/ +public interface ParserProtocolMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ProtocolMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ProtocolMapper.java new file mode 100644 index 0000000..832dc16 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ProtocolMapper.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 协议 Mapper 接口 + *

+ * + * @author admin + * @since 2025-09-02 + */ +public interface ProtocolMapper extends BaseMapper { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SchemeMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SchemeMapper.java new file mode 100644 index 0000000..6c56bb7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SchemeMapper.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.Scheme; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 方案 Mapper 接口 + *

+ * + * @author admin + * @since 2025-09-01 + */ +public interface SchemeMapper extends BaseMapper { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SensorEquipmentMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SensorEquipmentMapper.java new file mode 100644 index 0000000..4d4ece7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SensorEquipmentMapper.java @@ -0,0 +1,7 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cbsd.universaltestsoftware_client.entity.SensorEquipment; + +public interface SensorEquipmentMapper extends BaseMapper { +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ServerMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ServerMapper.java new file mode 100644 index 0000000..a770465 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/ServerMapper.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 服务端 Mapper 接口 + *

+ * + * @author admin + * @since 2025-08-26 + */ +public interface ServerMapper extends BaseMapper { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SystemConfigMapper.java b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SystemConfigMapper.java new file mode 100644 index 0000000..d572932 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/mapper/SystemConfigMapper.java @@ -0,0 +1,18 @@ +package com.cbsd.universaltestsoftware_client.mapper; + +import com.cbsd.universaltestsoftware_client.entity.SystemConfig; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author SanJie +* @description 针对表【system_config(系统配置(仅类型和内容))】的数据库操作Mapper +* @createDate 2025-09-19 11:18:42 +* @Entity com.cbsd.universaltestsoftware_client.entity.SystemConfig +*/ +public interface SystemConfigMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/config/NettyClientConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/config/NettyClientConfig.java new file mode 100644 index 0000000..b8587cf --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/config/NettyClientConfig.java @@ -0,0 +1,33 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Netty客户端配置类 + */ +@Configuration +public class NettyClientConfig { + + /** + * 创建线程池任务调度器,用于管理重连任务和异步操作 + */ + @Bean + public ThreadPoolTaskScheduler threadPoolTaskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(5); + scheduler.setThreadNamePrefix("netty-client-scheduler-"); + scheduler.setAwaitTerminationSeconds(60); + scheduler.setWaitForTasksToCompleteOnShutdown(true); + return scheduler; + } + @Bean + public ScheduledExecutorService scheduledExecutorService() { + ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + return scheduledExecutorService; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/config/ServerConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/config/ServerConfig.java new file mode 100644 index 0000000..2a5a23a --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/config/ServerConfig.java @@ -0,0 +1,19 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.config; + +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import lombok.Data; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 服务端配置信息 + */ +@Component +@Data +public class ServerConfig { + // 服务端配置集合,key为服务端标识,value为服务端信息 + private Map servers = new HashMap<>(); + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/connection/ConnectionManager.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/connection/ConnectionManager.java new file mode 100644 index 0000000..67364cd --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/connection/ConnectionManager.java @@ -0,0 +1,332 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.connection; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.cbsd.universaltestsoftware_client.entity.ChannelDataTraffic; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataTrafficMapper; +import com.cbsd.universaltestsoftware_client.mapper.ServerMapper; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.ReceiveTcpContent; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.SendTcpContent; +import com.cbsd.universaltestsoftware_client.parser.ParserManager; +import com.cbsd.universaltestsoftware_client.parser.ProtocolParserHelper; +import com.cbsd.universaltestsoftware_client.parser.model.OriginalData; +import com.cbsd.universaltestsoftware_client.service.ParserService; +import com.cbsd.universaltestsoftware_client.service.ServeService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.UUIDUtils; +import com.cbsd.universaltestsoftware_client.websocket.NoticeWebSocketHandler; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; + +/** + * 多服务端连接管理器:统一管理连接生命周期、消息转发、状态查询 + */ +@Component +public class ConnectionManager implements ServerConnection.ConnectionListener { + private static final Logger logger = LoggerFactory.getLogger(ConnectionManager.class); + + private final ScheduledExecutorService scheduler; // 重连调度线程池 + private final Map connections = new ConcurrentHashMap<>(); // 连接缓存(线程安全) + + /** + * 协议解析线程map + */ + private final Map protocolParserMap = new ConcurrentHashMap<>(); // 连接缓存(线程安全) + /** + * 线程安全的消息队列, 用于统计通道发送数据流量 + */ + private static final BlockingQueue tcpQueue = new LinkedBlockingQueue<>(); + + @Resource + private NoticeWebSocketHandler noticeWebSocketHandler; + + @Resource + private ServerMapper serverMapper; + + @Resource + private ParserManager parserManager; + @Resource + private ChannelDataTrafficMapper channelDataTrafficMapper; + /** + * 解析器service + */ + @Resource + private ParserService parserService; + + @Resource + private ServeService serveService; + + + // 构造器注入依赖 + @Autowired + public ConnectionManager(ScheduledExecutorService scheduler) { + this.scheduler = scheduler; + } + + /** + * 初始化数据库中所有连接 + */ + @PostConstruct + public void initConnections() { + new Thread(() -> { + Map servers = new HashMap<>(); + List serverInfos = serverMapper.selectList(new LambdaQueryWrapper().eq(ServerInfo::getIsDelete, "0") + .eq(ServerInfo::getConnectStatus, 1) + ); + for (ServerInfo serverInfo : serverInfos) { + servers.put(serverInfo.getServerId(), serverInfo); + } + + if (!servers.isEmpty()) { + servers.forEach(this::addAndConnectServer); +// logger.info("已初始化{}个服务端连接", servers.size()); + } else { +// logger.warn("未找到任何服务端配置,请在数添加"); + } + }).start(); + + new Thread(() -> { + //处理数据流量 + while (true) { + updateChannelDataTraffic(); + } + }).start(); + + } + + /** + * 添加并启动新服务端连接 + */ + public void addAndConnectServer(String serverId, ServerInfo serverInfo) { + if (connections.containsKey(serverId)) { +// logger.warn("服务端[{}]已存在,跳过重复添加", serverId); + return; + } + // 创建连接实例并加入缓存 + ServerConnection connection = new ServerConnection(serverId, serverInfo, scheduler, this); + connections.put(serverId, connection); + // 异步启动连接(避免阻塞主线程) + scheduler.execute(connection::connect); +// logger.info("已添加服务端[{}],正在启动连接...", serverId); + } + + /** + * 强制移除服务端连接(彻底删除,不触发重连) + */ + public void removeServer(String serverId) { + ServerConnection conn = connections.remove(serverId); + if (conn != null) { + conn.disconnect(); // 主动断开并清理资源 +// logger.info("已移除服务端[{}]及其连接", serverId); + } else { +// logger.warn("服务端[{}]不存在,无法移除", serverId); + } + } + + /** + * 主动断开连接(会触发自动重连) + */ + public void disconnectServer(String serverId) { + ServerConnection conn = connections.get(serverId); + if (conn != null) { + conn.disconnect(); +// logger.info("已主动断开服务端[{}]连接,将自动重连", serverId); + } else { +// logger.warn("服务端[{}]不存在,无法断开", serverId); + } + } + + /** + * 手动触发重连(修复:直接调用connect,避免重复断开) + */ + public void reconnectServer(String serverId) { + ServerConnection conn = connections.get(serverId); + if (conn != null) { + scheduler.execute(conn::connect); +// logger.info("已手动触发服务端[{}]重连", serverId); + } else { +// logger.warn("服务端[{}]不存在,无法重连", serverId); + } + } + + /** + * 向指定服务端发送十六进制消息(带参数校验) + */ + public void sendMessageToServer(String serverId, String hexMessage, String channelId, Integer portNumber, + SendTcpContent.OperationType operationType, String protocolXml, String extraData) { + // 前置参数校验 + if (serverId == null || serverId.trim().isEmpty()) { + logger.error("发送消息失败:服务端ID不能为空"); + return; + } + if (operationType == null) { + logger.error("发送消息失败:操作类型(operationType)不能为空"); + return; + } + // 获取连接并发送 + ServerConnection conn = connections.get(serverId); + if (conn != null) { + conn.sendHexMessage(hexMessage, channelId, portNumber, operationType, protocolXml, extraData); + } else { + logger.warn("服务端[{}]不存在,无法发送消息", serverId); + } + } + + /** + * 获取所有服务端ID + */ + public Set getAllServerIds() { + return connections.keySet(); + } + + /** + * 获取所有服务端连接状态(ID -> 连接状态) + */ + public Map getServerConnectionStatus() { + Map statusMap = new HashMap<>(connections.size()); + connections.forEach((serverId, conn) -> statusMap.put(serverId, conn.isConnected())); + return statusMap; + } + + /** + * 获取指定服务端连接实例 + */ + public ServerConnection getServerConnection(String serverId) { + return connections.get(serverId); + } + + /** + * 应用关闭时优雅清理所有连接(@PreDestroy:Spring容器销毁前执行) + */ + @PreDestroy + public void destroy() { +// logger.info("应用关闭中,开始清理所有服务端连接..."); + connections.values().forEach(ServerConnection::disconnect); + connections.clear(); +// logger.info("所有服务端连接已清理完成"); + } + + /* ===== 实现ServerConnection.ConnectionListener接口(连接事件回调)===== */ + @Override + public void onConnected(String serverId) { + logger.info("服务端[{}]连接成功", serverId); + } + + @Override + public void onDisconnected(String serverId) { + logger.warn("服务端[{}]连接断开,将自动重连", serverId); + reconnectServer(serverId); // 触发重连(调用修复后的reconnectServer) + } + + @Override + public void onConnectionFailed(String serverId) { + logger.error("服务端[{}]连接失败", serverId); + } + + @Override + public void onMessageReceived(String serverId, ReceiveTcpContent message) { + if (message == null || message.getOperationType() == null) { + return; + } + // 按消息类型转发(示例:通知消息通过WebSocket广播) + switch (message.getOperationType()) { + case DATA: + try { + tcpQueue.put(message); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // 数据消息处理(可扩展:如转发到业务服务) + String channelId = message.getChannelId(); + String dataBoardId = message.getDataBoardId(); + String key = serverId + "_" + channelId + "_" + dataBoardId + "_" + message.getExtraData(); + ProtocolParserHelper protocolParserHelper = protocolParserMap.get(key); + if (protocolParserHelper == null) { + protocolParserHelper = new ProtocolParserHelper(key, channelId, dataBoardId, 1, parserService, parserManager); + protocolParserMap.put(key, protocolParserHelper); + } + protocolParserHelper.putData(new OriginalData(dataBoardId, message.getBytesContent(), message.getTimestamp())); + break; + case NOTICE: + // 通知消息广播到WebSocket客户端(空校验避免NPE) + if (message.getBytesContent() != null) { + noticeWebSocketHandler.broadcast(new String(message.getBytesContent())); + } else { +// logger.warn("服务端[{}]通知消息字节数组为空", serverId); + } + break; + } + } + + @Override + public void oncloseConnected(String serverId) { + serveService.disconnectServer(serverId); + //推送给前端 + noticeWebSocketHandler.broadcast(serverId); + } + + /** + * 移除原始数据 + * + * @param dataBoardId 数据看板id + * + */ + public void removeOriginalData(String dataBoardId) { + for (ProtocolParserHelper helper : protocolParserMap.values()) { + helper.removeOriginalData(dataBoardId); + } + } + + /** + * 更新通道数据流量 + */ + private void updateChannelDataTraffic() { + try { + ReceiveTcpContent message = tcpQueue.take(); + String channelId = message.getChannelId(); + int byteLength = message.getBytesContent().length; + if (StringUtils.isNotBlank(channelId)) { + // 第一步:尝试更新计数(原子操作) + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .eq(ChannelDataTraffic::getChannelId, channelId) + .setSql("byte_count = byte_count + " + byteLength) + .set(ChannelDataTraffic::getUpdateTime, DateUtils.dateToString()); + + int updateCount = channelDataTrafficMapper.update(null, updateWrapper); + // 第二步:如果更新行数为0,说明记录不存在,执行插入 + if (updateCount == 0) { + ChannelDataTraffic channelDataTraffic = new ChannelDataTraffic(); + channelDataTraffic.setDataTrafficId(UUIDUtils.getUUID()); + channelDataTraffic.setChannelId(channelId); + channelDataTraffic.setByteCount(byteLength); + channelDataTraffic.setUpdateTime(DateUtils.dateToString()); + // 插入时加重试,避免并发插入冲突 + try { + channelDataTrafficMapper.insert(channelDataTraffic); + } catch (Exception e) { + // 插入失败(唯一键冲突),再次执行更新 + channelDataTrafficMapper.update(null, updateWrapper); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/connection/ServerConnection.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/connection/ServerConnection.java new file mode 100644 index 0000000..6569c8f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/connection/ServerConnection.java @@ -0,0 +1,650 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.connection; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.FrameDecoder; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.FrameEncoder; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.ReceiveTcpContent; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.SendTcpContent; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 服务端TCP连接管理类(高并发优化版) + * 重点优化大量消息的发送和接收性能 + */ +public class ServerConnection { + + private static final Logger logger = LoggerFactory.getLogger(ServerConnection.class); + + // 连接标识与配置 + private final String serverId; + private final ServerInfo serverInfo; + private final ScheduledExecutorService scheduler; + private final ConnectionListener connectionListener; + + // Netty核心组件 + private Channel channel; + private EventLoopGroup group; + private ScheduledFuture reconnectFuture; + // 用于接收消息的业务线程池(与IO线程分离) + private static final EventExecutorGroup businessExecutor = new DefaultEventExecutorGroup( + Runtime.getRuntime().availableProcessors() * 2, + new DefaultThreadFactory("business-executor-") + ); + + // 连接状态(线程安全) + private final AtomicInteger reconnectAttempts = new AtomicInteger(0); + private final AtomicBoolean isConnected = new AtomicBoolean(false); + private final AtomicBoolean isConnecting = new AtomicBoolean(false); + private final AtomicLong sendCounter = new AtomicLong(0); // 发送消息计数器 + private final AtomicLong receiveCounter = new AtomicLong(0); // 接收消息计数器 + + // 1. 消息发送优化:分级缓冲区 + 流量控制 + private final ConcurrentLinkedQueue highPriorityBuffer = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue normalBuffer = new ConcurrentLinkedQueue<>(); + private final int MAX_BUFFER_SIZE = 10000; // 总缓冲上限(根据内存调整) + private final AtomicInteger totalBuffered = new AtomicInteger(0); + private final AtomicBoolean isFlushing = new AtomicBoolean(false); // 防止重复触发缓冲刷新 + + // 2. 线程池优化:动态调整 + 合理参数 + private static final ThreadPoolExecutor sendWorkerPool = new ThreadPoolExecutor( + 4, // 核心线程数 + 16, // 最大线程数 + 30, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(5000), // 任务队列 + new DefaultThreadFactory("send-worker-"), + new ThreadPoolExecutor.CallerRunsPolicy() // 轻量拒绝策略,避免消息丢失 + ); + + /** + * 自定义线程工厂(统一命名 + 守护线程) + */ + static class DefaultThreadFactory implements ThreadFactory { + private final String prefix; + private final AtomicInteger threadNum = new AtomicInteger(1); + + public DefaultThreadFactory(String prefix) { + this.prefix = prefix; + } + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, prefix + threadNum.getAndIncrement()); + thread.setDaemon(true); // 守护线程,不阻塞JVM退出 + thread.setPriority(Thread.NORM_PRIORITY); + return thread; + } + } + + /** + * 构造器 + */ + public ServerConnection(String serverId, ServerInfo serverInfo, + ScheduledExecutorService scheduler, ConnectionListener connectionListener) { + this.serverId = serverId; + this.serverInfo = Objects.requireNonNull(serverInfo, "serverInfo cannot be null"); + this.scheduler = Objects.requireNonNull(scheduler, "scheduler cannot be null"); + this.connectionListener = connectionListener; + + // 初始化重连参数默认值 + if (this.serverInfo.getReconnectInitialDelay() <= 0) { + this.serverInfo.setReconnectInitialDelay(1000); + } + if (this.serverInfo.getReconnectMultiplier() <= 1.0) { + this.serverInfo.setReconnectMultiplier(2.0); + } + if (this.serverInfo.getReconnectMaxDelay() <= 0) { + this.serverInfo.setReconnectMaxDelay(30000); + } + } + + /** + * 建立与服务端的连接(优化Netty配置) + */ + public void connect() { + if (isConnected.get()) { +// logger.info("服务端[{}]已连接,无需重复连接", serverId); + return; + } + if (isConnecting.getAndSet(true)) { +// logger.info("服务端[{}]正在连接中,跳过本次请求", serverId); + return; + } + +// logger.info("开始连接服务端[{}]:{}:{}", serverId, serverInfo.getIpAddress(), serverInfo.getTcpPort()); + + // 校验服务端配置 + if (serverInfo == null || StringUtils.isBlank(serverInfo.getIpAddress()) || serverInfo.getTcpPort() <= 0) { +// logger.error("服务端[{}]配置无效:IP为空或端口非法", serverId); + isConnecting.set(false); + handleConnectionFailure(); + return; + } + + // 3. Netty配置优化:调整IO线程数 + TCP参数 + group = new NioEventLoopGroup(1); // 单个IO线程足够处理大量连接 + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioSocketChannel.class) + // 优化TCP参数 + .option(ChannelOption.TCP_NODELAY, true) // 禁用Nagle算法,降低延迟 + .option(ChannelOption.SO_KEEPALIVE, true) // 启用心跳检测 + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .option(ChannelOption.SO_RCVBUF, 1024 * 1024) // 接收缓冲区1MB + .option(ChannelOption.SO_SNDBUF, 1024 * 1024) // 发送缓冲区1MB + // 4. 写缓冲区水位线控制:防止OOM + .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( + 32 * 1024, // 低水位:32KB + 512 * 1024 // 高水位:512KB + )) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new FrameDecoder()); + pipeline.addLast(new FrameEncoder()); + // 5. 接收消息使用业务线程池,避免阻塞IO线程 + pipeline.addLast(businessExecutor, new ClientHandler(ServerConnection.this)); + } + }); + + // 发起异步连接 + ChannelFuture future = bootstrap.connect(serverInfo.getIpAddress(), serverInfo.getTcpPort()); + future.addListener((ChannelFutureListener) channelFuture -> { + isConnecting.set(false); + + if (channelFuture.isSuccess()) { + channel = channelFuture.channel(); + isConnected.set(true); + reconnectAttempts.set(0); +// logger.info("服务端[{}]连接成功", serverId); + + // 监听通道关闭事件 + channel.closeFuture().addListener(closeFuture -> handleDisconnect()); + + // 注册可写性监听器,缓冲区可用时自动发送 + channel.pipeline().addLast(new ChannelDuplexHandler() { + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) { + if (ctx.channel().isWritable()) { + triggerFlush(); // 通道可写时触发缓冲刷新 + } + ctx.fireChannelWritabilityChanged(); + } + }); + + if (connectionListener != null) { + connectionListener.onConnected(serverId); + } + // 连接成功后立即刷新缓冲消息 + triggerFlush(); + } else { +// logger.error("服务端[{}]连接失败", serverId, channelFuture.cause()); + handleConnectionFailure(); + } + }); + } + + /** + * 处理连接失败 + */ + private void handleConnectionFailure() { + isConnected.set(false); + cleanupResources(); + scheduleReconnect(); + + if (connectionListener != null) { + connectionListener.onConnectionFailed(serverId); + } + } + + /** + * 处理连接断开 + */ + private void handleDisconnect() { + if (isConnected.getAndSet(false)) { +// logger.warn("服务端[{}]连接已断开,累计发送:{}条,接收:{}条", +// serverId, sendCounter.get(), receiveCounter.get()); + cleanupResources(); + scheduleReconnect(); + + if (connectionListener != null) { + connectionListener.onDisconnected(serverId); + } + } + } + + /** + * 调度重连任务 + */ + public void scheduleReconnect() { + if (reconnectFuture != null && !reconnectFuture.isDone()) { + try { + reconnectFuture.cancel(false); + } catch (Exception e) { +// logger.error("服务端[{}]取消重连任务失败", serverId, e); + } finally { + reconnectFuture = null; + } + } + + int attempt = reconnectAttempts.incrementAndGet(); + long delay = (long) (serverInfo.getReconnectInitialDelay() * + Math.pow(serverInfo.getReconnectMultiplier(), attempt - 1)); + delay = Math.min(delay, serverInfo.getReconnectMaxDelay()); + delay = Math.max(delay, 1000); + +// logger.info("服务端[{}]第{}次重连将在{}ms后执行", serverId, attempt, delay); + //大于10次重连直接移除 + if (attempt > 10) { + //调用方法 + connectionListener.oncloseConnected(serverId); + } + try { + reconnectFuture = scheduler.schedule(this::connect, delay, TimeUnit.MILLISECONDS); + } catch (Exception e) { +// logger.error("服务端[{}]调度重连任务失败", serverId, e); + scheduler.execute(this::connect); + } + } + + /** + * 发送十六进制字符串消息 + */ + public void sendHexMessage(String message, String channelId, Integer portNumber, + SendTcpContent.OperationType operationType, String protocolXml, String extraData) { + try { + byte[] data; + if (SendTcpContent.OperationType.REGISTER == operationType) { + data = message.getBytes(); + } else { + data = hexStringToByteArray(message); + } + sendMessage(data, channelId, portNumber, operationType, protocolXml, extraData, false); + } catch (IllegalArgumentException e) { + logger.error("服务端[{}]解析十六进制消息失败", serverId, e); + } + } + + /** + * 发送字节数组消息(支持优先级) + */ + public void sendMessage(byte[] data, String channelId, Integer portNumber, + SendTcpContent.OperationType operationType, String protocolXml, + String extraData, boolean highPriority) { + if (data == null || data.length == 0) { + logger.error("服务端[{}]发送消息失败:字节数组为空", serverId); + return; + } + if (operationType == null) { + logger.error("服务端[{}]发送消息失败:操作类型不能为空", serverId); + return; + } + + SendTcpContent message = new SendTcpContent(data, channelId, operationType, + portNumber, protocolXml, extraData); + sendMessage(message, highPriority); + } + + /** + * 核心消息发送逻辑(高并发优化) + */ + public void sendMessage(SendTcpContent message, boolean highPriority) { + // 快速失败检查 + if (!isConnected.get()) { + logger.warn("服务端[{}]发送消息失败:未连接,缓冲消息", serverId); + bufferMessage(message, highPriority); + scheduleReconnect(); + return; + } + if (channel == null || !channel.isActive()) { + logger.error("服务端[{}]发送消息失败:通道无效,缓冲消息", serverId); + isConnected.set(false); + bufferMessage(message, highPriority); + scheduleReconnect(); + return; + } + + // 6. 非IO线程发送消息:提交到worker池避免阻塞调用线程 + sendWorkerPool.submit(() -> { + try { + // 通道可写时直接发送,否则缓冲 + if (channel.isWritable()) { + doSend(message); + } else { + bufferMessage(message, highPriority); + triggerFlush(); // 尝试触发一次刷新 + } + } catch (Exception e) { + logger.error("服务端[{}]发送任务执行失败", serverId, e); + bufferMessage(message, highPriority); + } + }); + } + + /** + * 实际发送消息(Netty IO线程中执行) + */ + private void doSend(SendTcpContent message) { + channel.writeAndFlush(message).addListener((ChannelFutureListener) future -> { + if (future.isSuccess()) { +// logger.info("服务端[{}]发送消息成功:{}", serverId, message); + // 发送成功后检查是否还有缓冲消息 + if (channel.isWritable()) { + triggerFlush(); + } + } else { + logger.error("服务端[{}]发送消息失败", serverId, future.cause()); + if (isConnected.get()) { + bufferMessage(message, true); // 失败消息提升为高优先级 + triggerFlush(); + } + } + }); + } + + /** + * 缓冲消息(分级存储) + */ + private void bufferMessage(SendTcpContent message, boolean highPriority) { + // 控制总缓冲大小,防止内存溢出 + if (totalBuffered.get() >= MAX_BUFFER_SIZE) { + // 缓冲区满时优先保留高优先级消息 + if (highPriority && !normalBuffer.isEmpty()) { + normalBuffer.poll(); // 移除一条普通消息 + totalBuffered.decrementAndGet(); + } else if (!highPriority) { + logger.warn("服务端[{}]普通消息缓冲区已满({}条),丢弃消息", + serverId, MAX_BUFFER_SIZE); + return; + } else { + logger.warn("服务端[{}]高优先级消息缓冲区已满({}条),丢弃消息", + serverId, MAX_BUFFER_SIZE); + return; + } + } + + // 按优先级加入不同缓冲区 + if (highPriority) { + highPriorityBuffer.offer(message); + } else { + normalBuffer.offer(message); + } + totalBuffered.incrementAndGet(); + } + + /** + * 触发缓冲消息刷新(防止并发重复执行) + */ + private void triggerFlush() { + if (isFlushing.compareAndSet(false, true)) { + // 提交到IO线程执行,避免线程安全问题 + if (channel != null && channel.eventLoop().inEventLoop()) { + flushBufferedMessages(); + } else if (channel != null) { + channel.eventLoop().execute(this::flushBufferedMessages); + } else { + isFlushing.set(false); + } + } + } + + /** + * 刷新缓冲消息(批量处理) + */ + private void flushBufferedMessages() { + try { + if (!isConnected.get() || channel == null || !channel.isWritable()) { + return; + } + + // 7. 批量发送:每次最多发送30条,避免长时间阻塞IO线程 + int maxBatch = 30; + int sent = 0; + + // 先发送高优先级消息 + SendTcpContent msg; + while ((msg = highPriorityBuffer.poll()) != null && sent < maxBatch) { + doSend(msg); + totalBuffered.decrementAndGet(); + sent++; + } + + // 再发送普通消息 + while ((msg = normalBuffer.poll()) != null && sent < maxBatch) { + doSend(msg); + totalBuffered.decrementAndGet(); + sent++; + } + + // 如果还有消息未发送,继续触发下一轮刷新 + if ((!highPriorityBuffer.isEmpty() || !normalBuffer.isEmpty()) && channel.isWritable()) { + channel.eventLoop().schedule(this::triggerFlush, 10, TimeUnit.MILLISECONDS); + } + } finally { + isFlushing.set(false); // 释放锁 + } + } + + /** + * 主动断开连接 + */ + public void disconnect() { +// logger.info("主动断开服务端[{}]连接,累计发送:{}条,接收:{}条", +// serverId, sendCounter.get(), receiveCounter.get()); + + // 取消重连任务 + if (reconnectFuture != null && !reconnectFuture.isDone()) { + reconnectFuture.cancel(false); + reconnectFuture = null; + } + + // 关闭通道 + if (channel != null && channel.isOpen()) { + channel.close().addListener(future -> { + if (future.isSuccess()) { +// logger.info("服务端[{}]通道关闭成功", serverId); + } else { +// logger.error("服务端[{}]通道关闭失败", serverId, future.cause()); + } + }); + } + + // 清理资源 + cleanupResources(); + isConnected.set(false); + isConnecting.set(false); + // 清空缓冲区 + highPriorityBuffer.clear(); + normalBuffer.clear(); + totalBuffered.set(0); + } + + /** + * 清理Netty资源 + */ + private void cleanupResources() { + if (group != null && !group.isShuttingDown()) { + group.shutdownGracefully(1000, 5000, TimeUnit.MILLISECONDS) + .addListener(future -> { + if (future.isSuccess()) { +// logger.info("服务端[{}]EventLoopGroup关闭成功", serverId); + } else { +// logger.error("服务端[{}]EventLoopGroup关闭失败", serverId, future.cause()); + } + }); + } + group = null; + channel = null; + } + + /** + * 处理接收到的消息(批量+异步) + */ + public void handleReceivedMessage(ReceiveTcpContent msg) { + if (msg == null) { +// logger.warn("服务端[{}]收到空消息", serverId); + return; + } + + // 8. 接收消息统计 + long count = receiveCounter.incrementAndGet(); + if (count % 1000 == 0) { +// logger.info("服务端[{}]累计接收消息:{}条", serverId, count); + } + + // 交给监听器处理(已在业务线程池执行) + if (connectionListener != null) { + connectionListener.onMessageReceived(serverId, msg); + } + } + + /** + * 处理通道异常 + */ + public void handleException(Throwable cause) { + logger.error("服务端[{}]通道发生异常", serverId, cause); + if (isConnected.get()) { + scheduleReconnect(); + } + } + + /* Getter方法 */ + public String getServerId() { + return serverId; + } + + public boolean isConnected() { + return isConnected.get(); + } + + public ServerInfo getServerInfo() { + return serverInfo; + } + + public int getBufferedMessageCount() { + return totalBuffered.get(); + } + + /** + * Netty客户端通道处理器(高并发优化) + */ + public static class ClientHandler extends SimpleChannelInboundHandler { + private final ServerConnection connection; + // 9. 批量接收缓冲区(减少业务线程切换) + private final ConcurrentLinkedQueue batchBuffer = new ConcurrentLinkedQueue<>(); + private final ScheduledFuture batchProcessor; + + public ClientHandler(ServerConnection connection) { + this.connection = connection; + // 定时批量处理接收的消息(每10ms或满100条时触发) + this.batchProcessor = businessExecutor.scheduleWithFixedDelay( + this::processBatchMessages, 10, 10, TimeUnit.MILLISECONDS); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ReceiveTcpContent msg) { + // 先加入批量缓冲区 + batchBuffer.offer(msg); + // 满100条时立即处理,避免延迟 + if (batchBuffer.size() >= 100) { + processBatchMessages(); + } + } + + /** + * 批量处理接收的消息,减少线程切换开销 + */ + private void processBatchMessages() { + int processed = 0; + ReceiveTcpContent msg; + while ((msg = batchBuffer.poll()) != null) { + connection.handleReceivedMessage(msg); + processed++; + } + if (processed > 0) { +// logger.trace("服务端[{}]批量处理消息:{}条", connection.serverId, processed); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { +// logger.error("通道异常,即将关闭", cause); + connection.handleException(cause); + ctx.close(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { +// logger.warn("通道已失效(服务端断开连接)"); + batchProcessor.cancel(false); // 停止批量处理器 + ctx.close(); + connection.handleDisconnect(); + } + } + + /** + * 十六进制字符串转字节数组 + */ + public static byte[] hexStringToByteArray(String hexString) { + if (StringUtils.isBlank(hexString)) { + return new byte[0]; + } + hexString = hexString.replaceAll("\\s", ""); + if (hexString.length() % 2 != 0) { + throw new IllegalArgumentException("十六进制字符串长度必须为偶数: " + hexString); + } + + int len = hexString.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + int high = Character.digit(hexString.charAt(i), 16); + int low = Character.digit(hexString.charAt(i + 1), 16); + if (high == -1 || low == -1) { + throw new IllegalArgumentException("无效的十六进制字符: " + hexString.substring(i, i + 2)); + } + data[i / 2] = (byte) ((high << 4) + low); + } + return data; + } + + /** + * 字节数组转十六进制字符串 + */ + public static String bytesToHex(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + sb.append(String.format("%02X", b)); + } + return sb.toString(); + } + + /** + * 连接事件监听接口 + */ + public interface ConnectionListener { + void onConnected(String serverId); + void onDisconnected(String serverId); + void onConnectionFailed(String serverId); + void onMessageReceived(String serverId, ReceiveTcpContent message); + + void oncloseConnected(String serverId); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/FrameDecoder.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/FrameDecoder.java new file mode 100644 index 0000000..d0166a3 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/FrameDecoder.java @@ -0,0 +1,56 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.entity; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class FrameDecoder extends ByteToMessageDecoder { + + // 帧头固定为4字节:0xCAFEBABE(十六进制) + private static final int FRAME_HEADER = 0xCAFEBABE; + private static final int HEADER_LENGTH = 4; // 帧头字节数 + private static final int LENGTH_FIELD_LENGTH = 4; // 长度字段字节数 + // JSON序列化工具 + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + // 1. 确保有足够的字节验证帧头(至少帧头长度+长度字段长度) + if (in.readableBytes() < HEADER_LENGTH + LENGTH_FIELD_LENGTH) { + return; // 字节不足,等待更多数据 + } + + // 2. 标记当前读指针位置,方便验证失败时重置 + in.markReaderIndex(); + + // 3. 读取帧头并验证 + int header = in.readInt(); // 读取4字节帧头 + if (header != FRAME_HEADER) { + // 帧头不匹配,重置读指针并跳过一个字节,然后重新尝试 + in.resetReaderIndex(); + in.readByte(); // 跳过一个字节 + // 递归调用decode继续处理剩余数据 + decode(ctx, in, out); + return; + } + + // 4. 读取长度字段(内容的字节数) + int contentLength = in.readInt(); + + // 5. 确保有足够的字节读取内容 + if (in.readableBytes() < contentLength) { + in.resetReaderIndex(); // 重置读指针,等待更多数据 + return; + } + + byte[] contentBytes = new byte[contentLength]; // 从长度字段获取contentLength + in.readBytes(contentBytes); + ReceiveTcpContent receiveTcpContent = objectMapper.readValue(contentBytes, ReceiveTcpContent.class); // 反序列化 + + out.add(receiveTcpContent); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/FrameEncoder.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/FrameEncoder.java new file mode 100644 index 0000000..497c371 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/FrameEncoder.java @@ -0,0 +1,27 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.entity; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class FrameEncoder extends MessageToByteEncoder { + + private static final int FRAME_HEADER = 0xCAFEBABE; + // JSON序列化工具 + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void encode(ChannelHandlerContext ctx, SendTcpContent content, ByteBuf out) throws JsonProcessingException { + byte[] contentBytes = objectMapper.writeValueAsBytes(content); + int contentLength = contentBytes.length; + // 1. 写入帧头(4字节) + out.writeInt(FRAME_HEADER); + // 2. 写入长度字段(4字节,内容的字节数) + out.writeInt(contentLength); + // 3. 写入内容 + out.writeBytes(contentBytes); // 写入序列化后的内容 + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/NoticeMessage.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/NoticeMessage.java new file mode 100644 index 0000000..b57a19a --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/NoticeMessage.java @@ -0,0 +1,12 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.entity; + +import lombok.Data; + +@Data +public class NoticeMessage { + //debug,info,warn,error + private String messageType; + private String message; + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/ReceiveTcpContent.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/ReceiveTcpContent.java new file mode 100644 index 0000000..9ead836 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/ReceiveTcpContent.java @@ -0,0 +1,93 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.entity; + +import com.baomidou.mybatisplus.generator.IFill; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * TCP传递消息体 + */ +@Data +public class ReceiveTcpContent { + + public enum OperationType{ + DATA(0),NOTICE(1); + + OperationType(int operationType) { + this.operationType = operationType; + } + private final int operationType; + public int getOperationType() { + return operationType; + } + + } + + /** + * 为空正常数据,不为空数据回访数据 + */ + private String dataBoardId; + + private byte[] bytesContent; + // 消息时间戳 + private long timestamp; + + /** + * 通道ID + */ + private String channelId; + + /** + * 额外数据 + */ + private String extraData; + + /** + * 端口号 + */ + private int portNumber; + + /** + * 操作类型 数据=0,寄存器=1 + */ + private OperationType operationType; + + // /** + // * 驱动类型 + // */ + // private int driverType; + // /** + // * 同一驱动类型下的不同设备类型(型号) + // */ + // private String deviceType; + /** + * 索引 编号 + */ + + + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append("TcpContent{"); + sb.append(" channelId=").append(channelId); + sb.append(", operationType=").append(operationType.getOperationType()); + sb.append(", dataBoardId=").append(dataBoardId); + sb.append(", portNumber=").append(portNumber).append(","); + if (bytesContent != null){ + for (byte elem : bytesContent) { + sb.append("bytesContent=").append(bytesToHex(elem)); + } + } + sb.append(", timestamp=").append(timestamp); + sb.append('}'); + return sb.toString(); + } + + // 将字节数组转换为十六进制字符串 + private String bytesToHex(byte b) { + return String.format("%02X ", b).trim(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/SendTcpContent.java b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/SendTcpContent.java new file mode 100644 index 0000000..03f623e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/nettyclient/entity/SendTcpContent.java @@ -0,0 +1,105 @@ +package com.cbsd.universaltestsoftware_client.nettyclient.entity; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * TCP传递消息体 + */ +@Data +public class SendTcpContent { + + public enum OperationType{ + DATA(0),REGISTER(1); + + OperationType(int operationType) { + this.operationType = operationType; + } + private final int operationType; + public int getOperationType() { + return operationType; + } + + } + private byte[] bytesContent; + + private String protocolXml; + // 消息时间戳 + private long timestamp; + + /** + * 通道ID + */ + private String channelId; + + /** + * 端口号 + */ + private int portNumber; + + /** + * 操作类型 数据=0,寄存器=1 + */ + private OperationType operationType; + + private String extraData; + + // /** + // * 驱动类型 + // */ + // private int driverType; + // /** + // * 同一驱动类型下的不同设备类型(型号) + // */ + // private String deviceType; + /** + * 索引 编号 + */ + // private int index; + public SendTcpContent() { + + } + + public SendTcpContent(byte[] bytesContent, String channelId, OperationType operationType, int portNumber,String protocolXml,String extraData) { + this.bytesContent = bytesContent; + this.channelId = channelId; + this.operationType = operationType; + this.portNumber = portNumber; + this.timestamp = System.currentTimeMillis(); + this.protocolXml = protocolXml; + this.extraData = extraData; + } + + + + + + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append("TcpContent{"); + sb.append(" channelId=").append(channelId); + sb.append(", operationType=").append(operationType); + sb.append(", portNumber=").append(portNumber); + sb.append("bytesContent=").append(bytesToHex(bytesContent)); + sb.append(", protocolXml=").append(protocolXml); + sb.append(", timestamp=").append(timestamp); + sb.append(", extraData=").append(extraData); + sb.append('}'); + return sb.toString(); + } + + // 将字节数组转换为十六进制字符串 + private String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString().trim(); + } + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserManager.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserManager.java new file mode 100644 index 0000000..1b2cc3b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserManager.java @@ -0,0 +1,161 @@ +package com.cbsd.universaltestsoftware_client.parser; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.cbsd.universaltestsoftware_client.entity.Parser; +import com.cbsd.universaltestsoftware_client.entity.ParserProtocol; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.cbsd.universaltestsoftware_client.mapper.ParserMapper; +import com.cbsd.universaltestsoftware_client.mapper.ParserProtocolMapper; +import com.cbsd.universaltestsoftware_client.mapper.ProtocolMapper; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.parser.model.Meta; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import com.cbsd.universaltestsoftware_client.util.XmlToObjectConverter; +import com.cbsd.universaltestsoftware_client.websocket.NoticeWebSocketHandler; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class ParserManager { + + @Resource + private ParserMapper parserMapper; + + @Resource + private ParserProtocolMapper parserProtocolMapper; + + @Resource + private ProtocolMapper protocolMapper; + + @Setter + private OnParserChangeListener onParserChangeListener; + + @Getter + @Value("${cbsd.sampling-interval-ms:1000}") + private long samplingIntervalMs; + + @Resource + private NoticeWebSocketHandler noticeWebSocketHandler; + + /** + * 解析器map + */ + private final Map parserMap = new HashMap<>(); + /** + * 解析器协议map + */ + private final Map> parserProtocolMap = new HashMap<>(); + /** + * 协议map + */ + private final Map protocolMap = new HashMap<>(); + + @PostConstruct + public void initParser() { + List parserList = parserMapper.selectList(new LambdaQueryWrapper().eq(Parser::getIsDelete, 0).orderByAsc(Parser::getParserSort)); + processParserList(parserList); + } + + private void processParserList(List parserList) { + parserProtocolMap.clear(); + parserMap.clear(); + protocolMap.clear(); + if (parserList != null && !parserList.isEmpty()) { + for (Parser parser : parserList) { + parserMap.put(parser.getParserId(), parser); + //解析器绑定的协议列表 + List parserProtocolList = parserProtocolMapper.selectList(new LambdaQueryWrapper() + .eq(ParserProtocol::getParserId, parser.getParserId())); + if (parserProtocolList != null && !parserProtocolList.isEmpty()) { + List protocolIdList = parserProtocolList.stream().map(ParserProtocol::getProtocolId).collect(Collectors.toList()); + List protocolList = protocolMapper.selectList(new LambdaQueryWrapper().in(Protocol::getProtocolId, protocolIdList)); + parserProtocolMap.put(parser.getParserId(), protocolList); + for (Protocol protocol : protocolList) { + try { + ProtocolXml protocolXml = XmlToObjectConverter.convertXmlToObject(protocol.getProtocolContent(), ProtocolXml.class); + Meta meta = protocolXml.getMeta(); + if (meta != null) { + List filedList = protocolXml.getFileds(); + filedList.sort(Comparator.comparingInt(Filed::getSnInt)); + Map filedMap = new HashMap<>(); + int totalBitslen = 0; + for (Filed filed : filedList) { + filedMap.put(filed.getType(), filed); + totalBitslen += filed.getBitsLen(); + } + meta.setDataLength(totalBitslen / 8); + protocolXml.setFileds(filedList); + protocolXml.setMeta(meta); + + protocol.setFiledMap(filedMap); + } + protocol.setProtocolXml(protocolXml); + } catch (Exception ignored) { + + } + protocolMap.put(protocol.getProtocolId(), protocol); + } + } + } + } + + if (onParserChangeListener != null) { + onParserChangeListener.onParserChanged(); + } + } + + /** + * 更新通道解析器 + * + */ + public void refresh() { + //先将该通道移除 + initParser(); + Map msgMap = new HashMap<>(); + msgMap.put("messageType", "PROTOCOL_CHANGED"); + msgMap.put("message", "协议改变, 刷新遥测及数据回放数据看板"); + noticeWebSocketHandler.broadcast(JSON.toJSONString(msgMap)); + } + + /** + * 获取解析器协议列表 + * + * @param parserId 解析器id + */ + public List getParserProtocolList(String parserId) { + return parserProtocolMap.get(parserId); + } + + /** + * 获取解析器 + * + * @param parserId 解析器id + */ + public Parser getParser(String parserId) { + return parserMap.get(parserId); + } + + /** + * 获取协议 + * + * @param protocolId 协议id + */ + public Protocol getProtocol(String protocolId) { + return protocolMap.get(protocolId); + } + + public interface OnParserChangeListener { + void onParserChanged(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserMessageQueueConsumer.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserMessageQueueConsumer.java new file mode 100644 index 0000000..bf3cdff --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserMessageQueueConsumer.java @@ -0,0 +1,125 @@ +package com.cbsd.universaltestsoftware_client.parser; + +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.cbsd.universaltestsoftware_client.entity.ChannelData; +import com.cbsd.universaltestsoftware_client.entity.ChannelDataHit; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataHitMapper; +import com.cbsd.universaltestsoftware_client.parser.model.ParserMessage; +import com.cbsd.universaltestsoftware_client.service.ChannelDataService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.UUIDUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Component +public class ParserMessageQueueConsumer { + + @Resource + private ChannelDataService channelDataService; + + @Resource + private ChannelDataHitMapper channelDataHitMapper; + + /** + * 已经处理的消息,避免重复处理 + */ + private final Set processedMessageIds = Collections.synchronizedSet(new HashSet<>()); + + @PostConstruct + private void start() { + new Thread(() -> { + while (true) { + processData(); + } + }).start(); + } + + private void processData() { + try { + ParserMessage message = ParserMessageQueueManager.takeMessage(); + if (!tryProcessMessage(message.getMessageId())) { + //已经处理过,直接跳过 + return; + } +// channelDataService.checkDataLimitToClear(); + + String channelId = message.getChannelId(); + if (StringUtils.isNotBlank(channelId)) { + // 始终原子更新hitCount(无论是否采样,每帧都计数) + updateHitCountAtomically(channelId, message.getProtocolId()); + + // 仅对采样帧保存ChannelData字段详情 + if (message.isSampled() && message.getFiledParseDataList() != null + && !message.getFiledParseDataList().isEmpty()) { + ChannelData channelData = new ChannelData(); + channelData.setDataId(message.getMessageId()); + channelData.setChannelId(channelId); + channelData.setData(JSON.toJSONString(message.getFiledParseDataList())); + channelData.setProtocolId(message.getProtocolId()); + channelData.setProtocolContent(message.getProtocolContent()); + channelData.setTime(DateUtils.timestampToDateString(message.getTimestamp(), "yyyy-MM-dd HH:mm:ss.SSS")); + try { + channelDataService.save(channelData); + } catch (Exception ignored) { + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 尝试处理消息,校验消息是否处理过 + * + * @param messageId + * @return + */ + private boolean tryProcessMessage(String messageId) { + // 超过1小时的记录清理,避免内存溢出 + if (processedMessageIds.size() > 10000) { + processedMessageIds.clear(); // 可优化为按时间清理 + } + return processedMessageIds.add(messageId); // add返回false表示已处理过 + } + + /** + * 原子更新hitCount,解决并发计数问题 + */ + private void updateHitCountAtomically(String channelId, String protocolId) { + // 第一步:尝试更新计数(原子操作) + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .eq(ChannelDataHit::getChannelId, channelId) + .eq(ChannelDataHit::getProtocolId, protocolId) + // 数据库层面的原子累加,避免丢失更新 + .setSql("hit_count = hit_count + 1") + .set(ChannelDataHit::getUpdateTime, DateUtils.dateToString()); + + int updateCount = channelDataHitMapper.update(null, updateWrapper); + + // 第二步:如果更新行数为0,说明记录不存在,执行插入 + if (updateCount == 0) { + ChannelDataHit dataHit = new ChannelDataHit(); + dataHit.setHitId(UUIDUtils.getUUID()); + dataHit.setChannelId(channelId); + dataHit.setProtocolId(protocolId); + dataHit.setHitCount(1); + dataHit.setUpdateTime(DateUtils.dateToString()); + + // 插入时加重试,避免并发插入冲突 + try { + channelDataHitMapper.insert(dataHit); + } catch (Exception e) { + // 插入失败(唯一键冲突),再次执行更新 + channelDataHitMapper.update(null, updateWrapper); + } + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserMessageQueueManager.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserMessageQueueManager.java new file mode 100644 index 0000000..2767b17 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserMessageQueueManager.java @@ -0,0 +1,86 @@ +package com.cbsd.universaltestsoftware_client.parser; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.ValueFilter; +import com.cbsd.universaltestsoftware_client.parser.model.ParserMessage; +import com.cbsd.universaltestsoftware_client.util.DataUtils; +import com.cbsd.universaltestsoftware_client.websocket.DataPlayBackWebSocketHandler; +import com.cbsd.universaltestsoftware_client.websocket.DataWebSocketHandler; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +@Component +public class ParserMessageQueueManager { + + // 线程安全的消息队列 + private static final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); + + // 用于接收 Spring 注入的实例 (非 static) + @Resource + private DataWebSocketHandler dataWebSocketHandlerInstance; + @Resource + private DataPlayBackWebSocketHandler dataPlayBackWebSocketHandler; + + // 用于 static 方法访问的 static 字段 + private static DataWebSocketHandler staticDataWebSocketHandler; + + private static DataPlayBackWebSocketHandler staticDataPlayBackWebSocketHandler; + + // 在 Bean 初始化完成后,将注入的实例赋值给 static 字段 + @PostConstruct + public void initStaticHandler() { + staticDataWebSocketHandler = this.dataWebSocketHandlerInstance; + staticDataPlayBackWebSocketHandler = this.dataPlayBackWebSocketHandler; + } + + // 添加消息到队列 + public static void pushMessage(ParserMessage message) { + if (StringUtils.isBlank(message.getDataBoardId())){ + try { + messageQueue.put(message); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // 使用 static 字段调用方法 + if (staticDataWebSocketHandler != null) { + //推送广播给前端,对象转化成字符串对象 + staticDataWebSocketHandler.broadcast(JSON.toJSONString(message)); +// staticDataWebSocketHandler.broadcast(JSON.toJSONString(message, (ValueFilter) (object, name, value) -> { +// if ("sourceValue".equalsIgnoreCase(name)){ +// return DataUtils.byteToHex((byte[])value); +// } +// return value; +// })); + } else { + // 可能发生在 Spring 上下文尚未完全初始化或配置错误时 + System.err.println("Warning: DataWebSocketHandler is not initialized!"); + } + } else { + if (staticDataPlayBackWebSocketHandler != null) { + staticDataPlayBackWebSocketHandler.broadcast(JSON.toJSONString(message)); +// staticDataPlayBackWebSocketHandler.broadcast(JSON.toJSONString(message, (ValueFilter) (object, name, value) -> { +// if ("sourceValue".equalsIgnoreCase(name)){ +// return DataUtils.byteToHex((byte[])value); +// } +// return value; +// })); + } else { + System.err.println("Warning: DataPlayBackWebSocketHandler is not initialized!"); + } + } + } + + // 从队列获取消息 + public static ParserMessage takeMessage() throws InterruptedException { + return messageQueue.take(); + } + + public static int getParserMessageCount() { + return messageQueue.size(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserThread.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserThread.java new file mode 100644 index 0000000..bca1b47 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ParserThread.java @@ -0,0 +1,184 @@ +package com.cbsd.universaltestsoftware_client.parser; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.cbsd.universaltestsoftware_client.entity.Parser; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.cbsd.universaltestsoftware_client.parser.model.OriginalData; +import com.cbsd.universaltestsoftware_client.service.ParserService; +import com.cbsd.universaltestsoftware_client.util.DataUtils; + +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.stream.Collectors; + +public class ParserThread extends Thread implements ParserManager.OnParserChangeListener { + /** + * 解析器service + */ + private final ParserService parserService; + /** + * 协议管理器 + */ + private final ParserManager parserManager; + /** + * 设备通道id + */ + private final String currentChannelId; + /** + * 前端数据回放看板Id, 为空正常数据,不为空数据回访数据 + */ + private final String dataBoardId; + /** + * 缓存数据 + */ + private final BlockingQueue dataQueue; + /** + * 解析器序号 + */ + private final int parserSort; + /** + * 当前解析器 + */ + private Parser currentParser; + + // TODO: 2025/9/26 更改为 BlockingQueue + private final List> protocolQueueList = new ArrayList<>(); + /** + * 协议线程map + */ + private final Map protocolThreadMap = new HashMap<>(); + + /** + * 构造函数 + * + * @param currentChannelId 设备通道id + * @param dataBoardId 回访看板Id + * @param dataQueue 数据队列 + * @param parserSort 解析器排序 + * @param parserService 解析器service + * @param parserManager 解析器管理器 + */ + public ParserThread(String currentChannelId, String dataBoardId, BlockingQueue dataQueue, int parserSort, ParserService parserService, ParserManager parserManager) { + this.currentChannelId = currentChannelId; + this.dataBoardId = dataBoardId; + this.dataQueue = dataQueue; + this.parserSort = parserSort; + this.parserService = parserService; + this.parserManager = parserManager; + + this.parserManager.setOnParserChangeListener(this); + } + + @Override + public void run() { + while (true) { + try { + pollToByteBuffer(); + } catch (Exception ignored) { + + } + } + } + + @Override + public void onParserChanged() { + checkProtocol(); + } + + /** + * 拉取队列数据到byteBuffer + */ + private void pollToByteBuffer() { + try { + OriginalData readData = dataQueue.take(); + for (Queue queue : protocolQueueList) { + queue.offer(readData); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 初始化 + */ + public boolean init() { + currentParser = parserService.getOne(new LambdaQueryWrapper() + .eq(Parser::getChannelId, currentChannelId) + .eq(Parser::getParserSort, parserSort), false); + if (currentParser != null) { + //解析器下的协议 + List protocolList = parserManager.getParserProtocolList(currentParser.getParserId()); + if (protocolList != null) { + for (Protocol protocol : protocolList) { + BlockingQueue queue = new LinkedBlockingQueue<>(); + protocolQueueList.add(queue); + ProtocolThread protocolThread = new ProtocolThread(currentChannelId, dataBoardId, protocol.getProtocolId(), queue, parserSort, parserService, parserManager); + protocolThread.start(); + protocolThreadMap.put(protocol.getProtocolId(), protocolThread); + } + return true; + } + } + return false; + + } + + /** + * 检查协议 + */ + public void checkProtocol() { + if (currentParser != null) { + List protocolList = parserManager.getParserProtocolList(currentParser.getParserId()); + if (protocolList != null) { + //先判断运行中的协议是否在解析器协议列表中, 不存在的则停止 + List protocolIdList = protocolList.stream().map(Protocol::getProtocolId).collect(Collectors.toList()); + List keyList = new ArrayList<>(protocolThreadMap.keySet()); + for (String runningProtocolId : keyList) { + if (!protocolIdList.contains(runningProtocolId)) { + //该解析器的协议列表已经不存在该协议, 停止 + ProtocolThread protocolThread = protocolThreadMap.get(runningProtocolId); + if (protocolThread != null) { + protocolThread.stopParse(); + protocolThreadMap.remove(runningProtocolId); + } + } + } + //添加新的协议 + for (Protocol protocol : protocolList) { + if (protocolThreadMap.get(protocol.getProtocolId()) == null) { + BlockingQueue queue = new LinkedBlockingQueue<>(); + protocolQueueList.add(queue); + ProtocolThread protocolThread = new ProtocolThread(currentChannelId, dataBoardId, protocol.getProtocolId(), queue, parserSort, parserService, parserManager); + protocolThread.start(); + protocolThreadMap.put(protocol.getProtocolId(), protocolThread); + } + } + } else { + //停止所有 + if (!protocolThreadMap.isEmpty()) { + for (ProtocolThread protocolThread : protocolThreadMap.values()) { + protocolThread.stopParse(); + } + protocolThreadMap.clear(); + } + } + } + } + + /** + * 移除原始数据 + * @param dataBoardId 数据看板id + */ + public void removeOriginalData(String dataBoardId){ + if (dataQueue != null){ + dataQueue.removeIf(originalData -> originalData.getDataBoardId().equals(dataBoardId)); + } + + for (ProtocolThread protocolThread : protocolThreadMap.values()){ + protocolThread.removeOriginalData(dataBoardId); + } + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/ProtocolParserHelper.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ProtocolParserHelper.java new file mode 100644 index 0000000..c9f0e2e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ProtocolParserHelper.java @@ -0,0 +1,48 @@ +package com.cbsd.universaltestsoftware_client.parser; + +import com.cbsd.universaltestsoftware_client.parser.model.OriginalData; +import com.cbsd.universaltestsoftware_client.service.ParserService; +import lombok.Data; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +@Data +public class ProtocolParserHelper { + /** + * 缓存数据 + */ + private final BlockingQueue dataQueue = new LinkedBlockingQueue<>(); + + private ParserThread parserThread; + + private String uniqueKey; + + public ProtocolParserHelper(String uniqueKey, String channelId, String dataBoardId, int parserSort, ParserService parserService, ParserManager parserManager) { + this.uniqueKey = uniqueKey; + parserThread = new ParserThread(channelId, dataBoardId, dataQueue, parserSort, parserService, parserManager); + if (parserThread.init()) { + parserThread.start(); + } + } + + public void putData(OriginalData data) { + try { + dataQueue.put(data); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 移除原始数据 + * + * @param dataBoardId 数据看板id + */ + public void removeOriginalData(String dataBoardId) { + dataQueue.removeIf(originalData -> originalData.getDataBoardId().equals(dataBoardId)); + if (parserThread != null) { + parserThread.removeOriginalData(dataBoardId); + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/ProtocolThread.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ProtocolThread.java new file mode 100644 index 0000000..8c5cb52 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/ProtocolThread.java @@ -0,0 +1,529 @@ +package com.cbsd.universaltestsoftware_client.parser; + +import com.cbsd.client.parse.IParseDataInterface; +import com.cbsd.client.verification.IVerificationInterface; +import com.cbsd.client.verification.VerifyUtils; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.cbsd.universaltestsoftware_client.parser.enums.ProtocolEnum; +import com.cbsd.universaltestsoftware_client.parser.model.*; +import com.cbsd.universaltestsoftware_client.script.ScriptManager; +import com.cbsd.universaltestsoftware_client.service.ParserService; +import com.cbsd.universaltestsoftware_client.util.DataUtils; +import com.cbsd.universaltestsoftware_client.util.UUIDUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class ProtocolThread extends Thread { + /** + * 解析器service + */ + private final ParserService parserService; + /** + * 协议管理器 + */ + private final ParserManager parserManager; + /** + * 当前通道id + */ + private final String currentChannelId; + /** + * 数据回放看板Id, 为空正常数据, 否则为回放数据 + */ + private final String dataBoardId; + /** + * 解析器序号 + */ + private final int parserSort; + /** + * /** + * 协议id + */ + private final String protocolId; + /** + * 原始数据队列 + */ + private final BlockingQueue dataQueue; + /** + * 处理数据 + */ + private final int BYTE_BUF_INITIAL_CAPACITY = 128; + private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private ByteBuf byteBuf = allocator.buffer(BYTE_BUF_INITIAL_CAPACITY); + + /** + * 当前正在解析的原始数据 + */ + private OriginalData currentParseOriginalData; + + private boolean isStop = false; + + private final long samplingIntervalMs; + private long lastSampledTimestamp = 0; + + /** + * 构造函数 + * + * @param channelId 通道id + * @param dataBoardId 数据回放看板Id + * @param protocolId 协议id + * @param dataQueue 数据队列 + * @param parserSort 解析器序号 + * @param parserService 解析器service + * @param parserManager 解析器管理器 + */ + public ProtocolThread(String channelId, String dataBoardId, String protocolId, BlockingQueue dataQueue, int parserSort, ParserService parserService, ParserManager parserManager) { + this.currentChannelId = channelId; + this.dataBoardId = dataBoardId; + this.protocolId = protocolId; + this.dataQueue = dataQueue; + this.parserSort = parserSort; + this.parserService = parserService; + this.parserManager = parserManager; + this.samplingIntervalMs = parserManager.getSamplingIntervalMs(); + setName(this.protocolId); + } + + @Override + public void run() { + super.run(); + while (true) { + try { + processData(); + } catch (Exception ignored) { + + } + + } + } + + /** + * 停止解析 + */ + public void stopParse() { + if (!isStop) { + isStop = true; + } + try { + join(1000); + interrupt(); + } catch (InterruptedException ignored) { + + } + } + + /** + * 处理数据 + */ + public void processData() { + //剩余空间 + int remaining = byteBuf.readableBytes(); + if (remaining > 0) { + dataParse(); + byteBufCompact(); + } else { + byteBufClear(); + pollToByteBuffer(); + } + } + + /** + * 1. **检查可读数据**:获取缓冲区中可读字节的长度 + * 2. **有数据时**:调用 `discardReadBytes()` 丢弃已读数据,将剩余数据移到缓冲区开头 + * 3. **无数据时**:直接调用 `byteBufClear()` 清空缓冲区 + * 目的是优化缓冲区内存使用,避免已读数据占用空间。 + */ + private void byteBufCompact() { + int readableLength = byteBuf.readableBytes(); + if (readableLength > 0) { + // 它会丢弃已读数据,并将剩余数据移动到缓冲区开头 + byteBuf.discardReadBytes(); + } else { + // 没有可读数据,直接清空 + byteBufClear(); + } + } + + /** + * 清空byteBuf + */ + private void byteBufClear() { + // 步骤1:释放原有缓冲区(关键,避免内存泄漏) + byteBuf.release(); + // 步骤2:重新创建 128 字节的缓冲区 + byteBuf = allocator.buffer(BYTE_BUF_INITIAL_CAPACITY); + } + + /** + * 重置byteBuf + */ + private void byteBufRewind() { + byteBuf.readerIndex(0); + byteBuf.writerIndex(0); + } + + /** + * 拉取队列数据到byteBuffer + */ + private void pollToByteBuffer() { + try { + currentParseOriginalData = dataQueue.take(); + byteBuf.writeBytes(currentParseOriginalData.getBytesContent()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 解析数据 + */ + private void dataParse() { + try { + //避免协议内容被更改,重新获取最新协议 + Protocol protocol = parserManager.getProtocol(protocolId); + if (protocol == null) { + //未找到协议 + return; + } + ProtocolXml protocolXml = protocol.getProtocolXml(); + //协议元数据 + Meta meta = protocolXml.getMeta(); + if (meta == null) { + //放弃解析当前协议 + return; + } + //协议字段列表 + List filedList = protocolXml.getFileds(); +// filedList.sort(Comparator.comparingInt(Filed::getSnInt)); + //key为字段类型的map + Map filedMap = protocol.getFiledMap(); +// for (Filed filed : filedList) { +// filedMap.put(filed.getType(), filed); +// meta.setDataLength(meta.getDataLength() + (filed.getBitsLen() / 8)); +// } + if (filedMap.containsKey(ProtocolEnum.VARIABLE_LENGTH.getValue())) { + //包含可变长度字段 + //尝试从头解析到可变长度字段 + int variableLengthIndex = 0; + for (Filed filed : filedList) { + variableLengthIndex += filed.getBitsLen() / 8; + if (filed.getType().equals(ProtocolEnum.VARIABLE_LENGTH.getValue())) { + break; + } + } + //判断剩余长度数据是否可满足需要数据长度 + if (byteBuf.readableBytes() < variableLengthIndex) { + pollToByteBuffer(); + return; + } + + //计算并匹配数据长度 + int dataLength = 0; + for (Filed filed : filedList) { + int byteLen = filed.getBitsLen() / 8; + //剩余数据长度不足 + if (byteBuf.readableBytes() < byteLen) { + return; + } + byte[] filedDataBytes = new byte[byteLen]; + byteBuf.readBytes(filedDataBytes); + + if (filed.getType().equals(ProtocolEnum.VARIABLE_LENGTH.getValue())) { + //解析可变长度字段值,并从可变长度描述列表中匹配数据长度 + String variableLengthValue = DataUtils.byteToHex(filedDataBytes); + if (meta.getVariableLengthDescList() != null && !meta.getVariableLengthDescList().isEmpty()) { + for (VariableLengthDesc desc : meta.getVariableLengthDescList()) { + if (desc.getValue().equals(variableLengthValue)) { + dataLength = desc.getLengthInt(); + break; + } + } + } + break; + } + } + //将解析出来的数据长度赋值给VAR字段,并重新结算dataLength + for (Filed filed : filedList) { + if (filed.getType().equals(ProtocolEnum.VAR.getValue())) { + filed.setBitslen(String.valueOf(dataLength)); + } + meta.setDataLength(meta.getDataLength() + (filed.getBitsLen() / 8)); + } + //保证后续数据从0开始 + byteBufRewind(); + } + + //判断剩余数据长度是否满足协议数据长度 + if (byteBuf.readableBytes() < meta.getDataLength()) { + //尝试判断帧头是否匹配,不匹配则将当前数据抛弃 + String p_metaFrameHeader = meta.getFrameHeadValue(); + if (StringUtils.isNotBlank(p_metaFrameHeader) && filedMap.get(ProtocolEnum.FRAME_HEADER.getValue()) != null) { + Filed frameHeadFiled = filedMap.get(ProtocolEnum.FRAME_HEADER.getValue()); + int headLength = frameHeadFiled.getBitsLen() / 8; + if (byteBuf.readableBytes() >= headLength) { + byte[] headBytes = new byte[headLength]; + byteBuf.readBytes(headBytes); + byte[] headBits = DataUtils.byteArrayToBitArray(headBytes); + String d_frameHeaderValue = getDFrameHeaderStringValue(frameHeadFiled.getBitsLen(), headBits); + if (!p_metaFrameHeader.equals(d_frameHeaderValue)) { + return; + } else { + byteBufRewind(); + } + } else { + byteBufClear(); + } + } + pollToByteBuffer(); + return; + } + //读取协议所有字节数 + byte[] totalBytes = new byte[meta.getDataLength()]; + byteBuf.readBytes(totalBytes); + //将所有字节转换为bit + byte[] totalBits = DataUtils.byteArrayToBitArray(totalBytes); + //校验数据 + List checkBitList = new ArrayList<>(); + //帧数据 + List sourceBitList = new ArrayList<>(); + + int checkStartSn = 0; + try { + checkStartSn = Integer.parseInt(meta.getCheckStartSn()); + } catch (Exception ignored) { + } + int checkEndSn = 0; + try { + checkEndSn = Integer.parseInt(meta.getCheckEndSn()); + } catch (Exception ignored) { + } + + int readPosition = 0; + for (Filed filed : filedList) { + int bitsLen = filed.getBitsLen(); + //剩余数据长度不足 + //读取该字段二进制 bit + byte[] filedDataBits = new byte[bitsLen]; + System.arraycopy(totalBits, readPosition, filedDataBits, 0, bitsLen); + readPosition += bitsLen; + + for (byte filedDataBit : filedDataBits) { + if (filed.getSnInt() >= checkStartSn && filed.getSnInt() <= checkEndSn) { + checkBitList.add(filedDataBit); + } + sourceBitList.add(filedDataBit); + } + filed.setBinStr(DataUtils.bitsToBinaryString(filedDataBits)); + filed.setFiledValue(filedDataBits); + } + if (isStop) { + return; + } + //协议元数据中帧头 + String p_metaFrameHeader = meta.getFrameHeadValue(); + if (StringUtils.isNotBlank(p_metaFrameHeader)) { + p_metaFrameHeader = p_metaFrameHeader.replace("0x", "").replace("0X", ""); +// int p_frameHeader = Integer.parseInt(p_metaFrameHeader.replace("0x", ""), 16); + //帧头 + if (filedMap.get(ProtocolEnum.FRAME_HEADER.getValue()) != null) { + Filed frameHeadFiled = filedMap.get(ProtocolEnum.FRAME_HEADER.getValue()); + String d_frameHeaderValue = getDFrameHeaderStringValue(frameHeadFiled.getBitsLen(), frameHeadFiled.getFiledValue()); + if (d_frameHeaderValue.equals(p_metaFrameHeader)) { + //帧头匹配成功 + //找到协议中的校验字段 + if (filedMap.get(ProtocolEnum.CALC.getValue()) != null) { + Filed calcFiled = filedMap.get(ProtocolEnum.CALC.getValue()); + try { + //获取校验脚本 + IVerificationInterface checkInterface = (IVerificationInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IVerificationInterface.getValue(), meta.getVerifyScript()).newInstance(); + byte[] checkBits = DataUtils.byteListToBytes(checkBitList); + byte[] checkBytes = DataUtils.bitArrayToByteArray(checkBits); + + byte[] calcBytes = DataUtils.bitArrayToByteArray(calcFiled.getFiledValue()); + boolean checkSuccess = checkInterface.verification(checkBytes, calcBytes, new VerifyUtils()); + if (checkSuccess) { + pushSampledParseResult(protocol, filedList, sourceBitList); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("该协议校验脚本异常:" + e.getMessage()); + } + } else { + pushSampledParseResult(protocol, filedList, sourceBitList); + } + } + } + } else { + pushSampledParseResult(protocol, filedList, sourceBitList); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 获取帧头数据 + * + * @param frameHeadFiled 帧头字段 + * @return 帧头数据 + */ + private long getDFrameHeaderValue(Filed frameHeadFiled) { + long d_frameHeaderValue = -1; + if (frameHeadFiled.getBitsLen() % 8 == 0) { + byte[] frameHeadBytes = DataUtils.bitArrayToByteArray(frameHeadFiled.getFiledValue()); + if (frameHeadFiled.getEndian().equalsIgnoreCase(ProtocolEnum.ENDIAN_BIG.getValue())) { + //大端序 + d_frameHeaderValue = DataUtils.toIntBigEndian(frameHeadBytes); + } else { + //小端序 + d_frameHeaderValue = DataUtils.toIntLittleEndian(frameHeadBytes); + } + } + return d_frameHeaderValue; + } + + private String getDFrameHeaderStringValue(int bitsLength, byte[] bitArr) { + String d_frameHeaderValue = ""; + if (bitsLength % 8 == 0) { + byte[] frameHeadBytes = DataUtils.bitArrayToByteArray(bitArr); + d_frameHeaderValue = DataUtils.byteToHex(frameHeadBytes); + } + return d_frameHeaderValue; + } + + /** + * 解析字段物理数据 + * + * @param filedList 字段列表 + * @param sourceBytes 源数据 + */ + private List parseFiledPhysicsData(List filedList, byte[] sourceBytes) { + //获取解析数据脚本 + List filedParseDataList = new ArrayList<>(); + for (Filed filed : filedList) { + //判断字段类型是否为解析字段 +// if (filed.getType().equalsIgnoreCase(ProtocolEnum.VAR.getValue())) { +// boolean isByte = filed.getBitsLen() % 8 == 0; +// String parserResultValue; +// if (isByte) { +// //8的倍数,按字节解析 +// parserResultValue = DataUtils.byteToHex(DataUtils.bitArrayToByteArray(filed.getFiledValue())); +// } else { +// //非8的倍数,按位解析 +// parserResultValue = DataUtils.bitsToBinaryString(filed.getFiledValue()); +// } +// +// if (StringUtils.isNotBlank(filed.getPhyScript())) { +// //字段解析脚本 +// try { +// IParseDataInterface parserDataInterface = (IParseDataInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IParseDataInterface.getValue(), filed.getPhyScript()).newInstance(); +// if (isByte) { +// //8的倍数,按字节解析 +// parserResultValue = parserDataInterface.parsePhysicalFromData(filed.getSnInt(), sourceBytes, DataUtils.bitArrayToByteArray(filed.getFiledValue()), filed.getEndian(), filed.getBinStr().replaceAll(" ", "")); +// } else { +// //非8的倍数,按位解析 +// parserResultValue = parserDataInterface.parsePhysicalFromData(filed.getSnInt(), sourceBytes, filed.getFiledValue(), filed.getEndian(), filed.getBinStr().replaceAll(" ", "")); +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// if (isByte) { +// //8的倍数,按字节解析 +// filedParseDataList.add(new FiledParseData(DataUtils.byteToHex(DataUtils.bitArrayToByteArray(filed.getFiledValue())) + "(十六)", parserResultValue, filed.getLabel(), filed.getReadName(), filed.getPhyUnit(), filed.getPhyType())); +// } else { +// //非8的倍数,按位解析 +// filedParseDataList.add(new FiledParseData(filed.getBinStr() + "(二)", parserResultValue, filed.getLabel(), filed.getReadName(), filed.getPhyUnit(), filed.getPhyType())); +// } +// } else + if (filed.getType().equalsIgnoreCase(ProtocolEnum.FRAME.getValue())) { + //下一个解析器 + BlockingQueue framePipList = new LinkedBlockingQueue<>(); + try { + framePipList.put(new OriginalData(currentParseOriginalData.getDataBoardId(), filed.getFiledValue(), currentParseOriginalData.getTimestamp())); + ParserThread nextParserThread = new ParserThread(currentChannelId, dataBoardId, framePipList, parserSort + 1, parserService, parserManager); + if (nextParserThread.init()) { + nextParserThread.start(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + boolean isByte = filed.getBitsLen() % 8 == 0; + String parserResultValue; + if (isByte) { + //8的倍数,按字节解析 + parserResultValue = DataUtils.byteToHex(DataUtils.bitArrayToByteArray(filed.getFiledValue())); + } else { + //非8的倍数,按位解析 + parserResultValue = DataUtils.bitsToBinaryString(filed.getFiledValue()); + } + + if (StringUtils.isNotBlank(filed.getPhyScript())) { + //字段解析脚本 + try { + IParseDataInterface parserDataInterface = (IParseDataInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IParseDataInterface.getValue(), filed.getPhyScript()).newInstance(); + if (isByte) { + //8的倍数,按字节解析 + parserResultValue = parserDataInterface.parsePhysicalFromData(filed.getSnInt(), sourceBytes, DataUtils.bitArrayToByteArray(filed.getFiledValue()), filed.getEndian(), filed.getBinStr().replaceAll(" ", "")); + } else { + //非8的倍数,按位解析 + parserResultValue = parserDataInterface.parsePhysicalFromData(filed.getSnInt(), sourceBytes, filed.getFiledValue(), filed.getEndian(), filed.getBinStr().replaceAll(" ", "")); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + if (isByte) { + //8的倍数,按字节解析 + filedParseDataList.add(new FiledParseData(DataUtils.byteToHex(DataUtils.bitArrayToByteArray(filed.getFiledValue())) + "(十六)", parserResultValue, filed.getLabel(), filed.getReadName(), filed.getPhyUnit(), filed.getPhyType())); + } else { + //非8的倍数,按位解析 + filedParseDataList.add(new FiledParseData(filed.getBinStr() + "(二)", parserResultValue, filed.getLabel(), filed.getReadName(), filed.getPhyUnit(), filed.getPhyType())); + } + } + } + return filedParseDataList; + } + + /** + * 采样推送解析结果 -- 根据时间间隔决定是否完整解析字段 + */ + private void pushSampledParseResult(Protocol protocol, List filedList, List sourceBitList) { + long now = System.currentTimeMillis(); + boolean shouldSample = (now - lastSampledTimestamp) >= samplingIntervalMs; + + List filedParseDataList; + if (shouldSample) { + lastSampledTimestamp = now; + byte[] sourceBits = DataUtils.byteListToBytes(sourceBitList); + byte[] sourceBytes = DataUtils.bitArrayToByteArray(sourceBits); + filedParseDataList = parseFiledPhysicsData(filedList, sourceBytes); + } else { + filedParseDataList = Collections.emptyList(); + } + + ParserMessage msg = new ParserMessage(UUIDUtils.getUUID(), getName(), currentChannelId, dataBoardId, filedParseDataList, protocol.getProtocolId(), protocol.getProtocolContent(), currentParseOriginalData.getTimestamp(), shouldSample); + ParserMessageQueueManager.pushMessage(msg); + } + + /** + * 移除原始数据 + * + * @param dataBoardId 数据看板id + */ + public void removeOriginalData(String dataBoardId) { + if (dataQueue != null) { + dataQueue.removeIf(originalData -> originalData.getDataBoardId().equals(dataBoardId)); + } + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/enums/ProtocolEnum.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/enums/ProtocolEnum.java new file mode 100644 index 0000000..6d97d03 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/enums/ProtocolEnum.java @@ -0,0 +1,59 @@ +package com.cbsd.universaltestsoftware_client.parser.enums; + +import lombok.Getter; + +@Getter +public enum ProtocolEnum { + /** + * 协议头 + */ + FRAME_HEADER("HEAD"), + /** + * 长度 + */ + LENGTH("LENGTH"), + /** + * 可变长度 + */ + VARIABLE_LENGTH("VARIABLE_LENGTH"), + /** + * 计算 + */ + CALC("CALC"), + /** + * 变量 + */ + VAR("VAR"), + /** + * 帧 + */ + FRAME("FRAME"), + /** + * 帧尾尾 + */ + TAIL ("TAIL "), + /** + * 大端 + */ + ENDIAN_BIG("BIG"), + /** + * 小端 + */ + ENDIAN_LITTLE("LITTLE"); + + private final String value; + + ProtocolEnum(String value) { + this.value = value; + } + + public static ProtocolEnum getByValue(String value) { + for (ProtocolEnum item : ProtocolEnum.values()) { + if (item.value.equals(value)) { + return item; + } + } + return null; + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Filed.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Filed.java new file mode 100644 index 0000000..21a1fe8 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Filed.java @@ -0,0 +1,302 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import com.alibaba.fastjson.JSON; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Filed { + @XmlElement(name = "sn") + private String sn = ""; + /** + * 变量名 + */ + @XmlElement(name = "label") + private String label = ""; + /** + * 中文现实名称 + */ + @XmlElement(name = "read_name") + private String readName = ""; + /** + * 物理类型 int, unsigned int, float,double,hex,time + */ + @XmlElement(name = "phy_type") + private String phyType = ""; + /** + * 时间格式 + */ + @XmlElement(name = "time_format") + private String timeFormat = ""; + /** + * 字段的占用的比特长度 + */ + @XmlElement(name = "bitslen") + private String bitslen; + /** + * 1 代表对应位的值不关心,在转化成物理值和后续计算、验证 + * 时都不使用 + */ + @XmlElement(name = "bitmask") + private String bitmask = ""; + /** + * 内置脚本 + */ + @XmlElement(name = "phy_inner_script") + private String phyInnerScript = ""; + /** + * 将码流转换为物理值 + */ + @XmlElement(name = "phy_script") + private String phyScript = ""; + @XmlElement(name = "phy_script_file") + private String phyScriptFile = ""; + @XmlElement(name = "endian") + private String endian = ""; + @XmlElement(name = "type") + private String type = ""; + @XmlElement(name = "frame") + private String frame = ""; + @XmlElement(name = "calc_script") + private String calcScript = ""; + @XmlElement(name = "calc_script_file") + private String calcScriptFile = ""; + @XmlElement(name = "calc_script_args") + private String calcScriptArgs = ""; + @XmlElement(name = "phy_unit") + private String phyUnit = ""; + @XmlElement(name = "bin_str") + private String binStr = ""; + @XmlElement(name = "hex") + private String hex = ""; + @XmlElement(name = "phy") + private String phy = ""; + @XmlElement(name = "avg") + private String avg = ""; + @XmlElement(name = "min") + private String min = ""; + @XmlElement(name = "max") + private String max = ""; + /** + * 存储字段值 + */ + private byte[] filedData; + + public Filed() { + } + + public String getSn() { + return sn; + } + + public int getSnInt() { + try { + return Integer.parseInt(sn); + }catch (Exception e) { + return 0; + } + } + + public void setSn(String sn) { + this.sn = sn; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getReadName() { + return readName; + } + + public void setReadName(String readName) { + this.readName = readName; + } + + public String getPhyType() { + return phyType; + } + + public void setPhyType(String phyType) { + this.phyType = phyType; + } + + public String getTimeFormat() { + return timeFormat; + } + + public void setTimeFormat(String timeFormat) { + this.timeFormat = timeFormat; + } + + public String getBitslen() { + return bitslen; + } + + public int getBitsLen() { + try { + return Integer.parseInt(bitslen); + }catch (Exception e) { + return 0; + } + } + + public void setBitslen(String bitslen) { + this.bitslen = bitslen; + } + + public String getBitmask() { + return bitmask; + } + + public void setBitmask(String bitmask) { + this.bitmask = bitmask; + } + + public String getPhyInnerScript() { + return phyInnerScript; + } + + public void setPhyInnerScript(String phyInnerScript) { + this.phyInnerScript = phyInnerScript; + } + + public String getPhyScript() { + return phyScript; + } + + public void setPhyScript(String phyScript) { + this.phyScript = phyScript; + } + + public String getPhyScriptFile() { + return phyScriptFile; + } + + public void setPhyScriptFile(String phyScriptFile) { + this.phyScriptFile = phyScriptFile; + } + + public String getEndian() { + return endian; + } + + public void setEndian(String endian) { + this.endian = endian; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFrame() { + return frame; + } + + public void setFrame(String frame) { + this.frame = frame; + } + + public String getCalcScript() { + return calcScript; + } + + public void setCalcScript(String calcScript) { + this.calcScript = calcScript; + } + + public String getCalcScriptFile() { + return calcScriptFile; + } + + public void setCalcScriptFile(String calcScriptFile) { + this.calcScriptFile = calcScriptFile; + } + + public String getCalcScriptArgs() { + return calcScriptArgs; + } + + public void setCalcScriptArgs(String calcScriptArgs) { + this.calcScriptArgs = calcScriptArgs; + } + + public String getPhyUnit() { + return phyUnit; + } + + public void setPhyUnit(String phyUnit) { + this.phyUnit = phyUnit; + } + + public String getBinStr() { + return binStr; + } + + public void setBinStr(String binStr) { + this.binStr = binStr; + } + + public String getHex() { + return hex; + } + + public void setHex(String hex) { + this.hex = hex; + } + + public String getPhy() { + return phy; + } + + public void setPhy(String phy) { + this.phy = phy; + } + + public String getAvg() { + return avg; + } + + public void setAvg(String avg) { + this.avg = avg; + } + + public String getMin() { + return min; + } + + public void setMin(String min) { + this.min = min; + } + + public String getMax() { + return max; + } + + public void setMax(String max) { + this.max = max; + } + + public byte[] getFiledValue() { + return filedData; + } + + public void setFiledValue(byte[] value) { + this.filedData = value; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/FiledParseData.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/FiledParseData.java new file mode 100644 index 0000000..0032d81 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/FiledParseData.java @@ -0,0 +1,29 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FiledParseData { + + @ApiModelProperty("原始数据") + private String sourceValue; + // @ApiModelProperty("16进制数据") +// private String hexValue; + @ApiModelProperty("物理数据") + private String phyValue; + @ApiModelProperty("变量名") + private String label; + @ApiModelProperty("读取字段名称") + private String readName; + @ApiModelProperty("物理数据单位") + private String phyUnit; + @ApiModelProperty("物理数据类型") + private String phyType; + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Filed_Back.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Filed_Back.java new file mode 100644 index 0000000..56caf71 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Filed_Back.java @@ -0,0 +1,283 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import com.alibaba.fastjson.JSON; + +import javax.xml.bind.annotation.XmlElement; + +public class Filed_Back { + private int sn; + /** + * 变量名 + */ + private String label; + /** + * 中文现实名称 + */ + private String readName; + /** + * 物理类型 int, unsigned int, float,double,hex,time + */ + private String phyType; + /** + * 时间格式 + */ + private String timeFormat; + /** + * 字段的占用的比特长度 + */ + private int bitslen; + /** + * 1 代表对应位的值不关心,在转化成物理值和后续计算、验证 + * 时都不使用 + */ + private String bitmask; + /** + * 内置脚本 + */ + private String phyInnerScript; + /** + * 将码流转换为物理值 + */ + private String phyScript; + private String phyScriptFile; + private String endian; + private String type; + private String frame; + private String calcScript; + private String calcScriptFile; + private String calcScriptArgs; + private String phyUnit; + private String binStr; + private String hex; + private String phy; + private String avg; + private String min; + private String max; + /** + * 存储字段值 + */ + private byte[] filedData; + + public Filed_Back() { + } + + @XmlElement(name = "sn") + public int getSn() { + return sn; + } + + public void setSn(int sn) { + this.sn = sn; + } + + @XmlElement(name = "label") + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + @XmlElement(name = "read_name") + public String getReadName() { + return readName; + } + + public void setReadName(String readName) { + this.readName = readName; + } + + @XmlElement(name = "phy_type") + public String getPhyType() { + return phyType; + } + + public void setPhyType(String phyType) { + this.phyType = phyType; + } + + @XmlElement(name = "time_format") + public String getTimeFormat() { + return timeFormat; + } + + public void setTimeFormat(String timeFormat) { + this.timeFormat = timeFormat; + } + + @XmlElement(name = "bitslen") + public int getBitslen() { + return bitslen; + } + + public void setBitslen(int bitslen) { + this.bitslen = bitslen; + } + + @XmlElement(name = "bitmask") + public String getBitmask() { + return bitmask; + } + + public void setBitmask(String bitmask) { + this.bitmask = bitmask; + } + + @XmlElement(name = "phy_inner_script") + public String getPhyInnerScript() { + return phyInnerScript; + } + + public void setPhyInnerScript(String phyInnerScript) { + this.phyInnerScript = phyInnerScript; + } + + @XmlElement(name = "phy_script") + public String getPhyScript() { + return phyScript; + } + + public void setPhyScript(String phyScript) { + this.phyScript = phyScript; + } + + @XmlElement(name = "phy_script_file") + public String getPhyScriptFile() { + return phyScriptFile; + } + + public void setPhyScriptFile(String phyScriptFile) { + this.phyScriptFile = phyScriptFile; + } + + @XmlElement(name = "endian") + public String getEndian() { + return endian; + } + + public void setEndian(String endian) { + this.endian = endian; + } + + @XmlElement(name = "type") + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @XmlElement(name = "frame") + public String getFrame() { + return frame; + } + + public void setFrame(String frame) { + this.frame = frame; + } + + @XmlElement(name = "calc_script") + public String getCalcScript() { + return calcScript; + } + + public void setCalcScript(String calcScript) { + this.calcScript = calcScript; + } + + @XmlElement(name = "calc_script_file") + public String getCalcScriptFile() { + return calcScriptFile; + } + + public void setCalcScriptFile(String calcScriptFile) { + this.calcScriptFile = calcScriptFile; + } + + @XmlElement(name = "calc_script_args") + public String getCalcScriptArgs() { + return calcScriptArgs; + } + + public void setCalcScriptArgs(String calcScriptArgs) { + this.calcScriptArgs = calcScriptArgs; + } + + @XmlElement(name = "phy_unit") + public String getPhyUnit() { + return phyUnit; + } + + public void setPhyUnit(String phyUnit) { + this.phyUnit = phyUnit; + } + + @XmlElement(name = "bin_str") + public String getBinStr() { + return binStr; + } + + public void setBinStr(String binStr) { + this.binStr = binStr; + } + + @XmlElement(name = "hex") + public String getHex() { + return hex; + } + + public void setHex(String hex) { + this.hex = hex; + } + + @XmlElement(name = "phy") + public String getPhy() { + return phy; + } + + public void setPhy(String phy) { + this.phy = phy; + } + + @XmlElement(name = "avg") + public String getAvg() { + return avg; + } + + public void setAvg(String avg) { + this.avg = avg; + } + + @XmlElement(name = "min") + public String getMin() { + return min; + } + + public void setMin(String min) { + this.min = min; + } + + @XmlElement(name = "max") + public String getMax() { + return max; + } + + public void setMax(String max) { + this.max = max; + } + + public byte[] getFiledValue() { + return filedData; + } + + public void setFiledValue(byte[] value) { + this.filedData = value; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/FrameHeader.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/FrameHeader.java new file mode 100644 index 0000000..2659bf2 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/FrameHeader.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import lombok.Data; + +/** + * 帧头 + */ +@Data +public class FrameHeader { + /** + * 值 + */ + private String value; + /** + * 长度 + */ + private int frameHeaderLength; + /** + * 协议id + */ + private String protocolId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Meta.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Meta.java new file mode 100644 index 0000000..d1ddd57 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/Meta.java @@ -0,0 +1,82 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import javax.xml.bind.annotation.*; +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Meta { + @XmlElement(name = "frame_head_value") + private String frameHeadValue = ""; + @XmlElement(name = "frame_tail_value") + private String frameTailValue = ""; + @XmlElement(name = "check_start_sn") + private String checkStartSn = ""; + @XmlElement(name = "check_end_sn") + private String checkEndSn = ""; + @XmlElement(name = "verify_script") + private String verifyScript = ""; + @XmlElementWrapper(name = "variable_length_desc") + @XmlElement(name = "desc") + private List variableLengthDescList; + /** + * 数据总长度 + */ + @XmlTransient + private int dataLength; + + public String getFrameHeadValue() { + return frameHeadValue; + } + + public void setFrameHeadValue(String frameHeadValue) { + this.frameHeadValue = frameHeadValue; + } + + public String getFrameTailValue() { + return frameTailValue; + } + + public void setFrameTailValue(String frameTailValue) { + this.frameTailValue = frameTailValue; + } + + public String getCheckStartSn() { + return checkStartSn; + } + + public void setCheckStartSn(String checkStartSn) { + this.checkStartSn = checkStartSn; + } + + public String getCheckEndSn() { + return checkEndSn; + } + + public void setCheckEndSn(String checkEndSn) { + this.checkEndSn = checkEndSn; + } + + public String getVerifyScript() { + return verifyScript; + } + + public void setVerifyScript(String verifyScript) { + this.verifyScript = verifyScript; + } + + public List getVariableLengthDescList() { + return variableLengthDescList; + } + + public void setVariableLengthDescList(List variableLengthDescList) { + this.variableLengthDescList = variableLengthDescList; + } + + public int getDataLength() { + return dataLength; + } + + public void setDataLength(int dataLength) { + this.dataLength = dataLength; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/OriginalData.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/OriginalData.java new file mode 100644 index 0000000..40cdd09 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/OriginalData.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OriginalData { + + private String dataBoardId; + + private byte[] bytesContent; + + private long timestamp; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/ParserMessage.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/ParserMessage.java new file mode 100644 index 0000000..ceebc6c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/ParserMessage.java @@ -0,0 +1,32 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ParserMessage { + + private String messageId; + + private String threadName; + + private String channelId; + + private String dataBoardId; + + private List filedParseDataList; + + private String protocolId; + + private String protocolContent; + + private long timestamp; + + private boolean sampled; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/ProtocolXml.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/ProtocolXml.java new file mode 100644 index 0000000..04b606b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/ProtocolXml.java @@ -0,0 +1,53 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement(name = "protocol") +@XmlAccessorType(XmlAccessType.FIELD) +public class ProtocolXml { + @XmlElement(name = "read_name") + private String readName = ""; + @XmlElement(name = "meta") + private Meta meta; + @XmlElementWrapper(name = "fileds") + @XmlElement(name = "filed") + private List fileds; + + public ProtocolXml() { + this.fileds = new ArrayList<>(); + } + + public String getReadName() { + return readName; + } + + public void setReadName(String readName) { + this.readName = readName; + } + + public Meta getMeta() { + return meta; + } + + public void setMeta(Meta meta) { + this.meta = meta; + } + + public List getFileds() { + return fileds; + } + + public void setFileds(List fileds) { + this.fileds = fileds; + } + + @Override + public String toString() { + return "Protocol{" + + "readName='" + readName + '\'' + + ", fileds.size=" + fileds.size() + + '}'; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/VariableLengthDesc.java b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/VariableLengthDesc.java new file mode 100644 index 0000000..29f736e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/parser/model/VariableLengthDesc.java @@ -0,0 +1,39 @@ +package com.cbsd.universaltestsoftware_client.parser.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +/** + * 可变字段描述 + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class VariableLengthDesc { + @XmlElement(name = "value") + private String value = ""; + @XmlElement(name = "length") + private String length; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getLength() { + return length; + } + + public int getLengthInt() { + try { + return Integer.parseInt(length); + }catch (Exception e) { + return 0; + } + } + + public void setLength(String length) { + this.length = length; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/AccumulatePreTreatment.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/AccumulatePreTreatment.java new file mode 100644 index 0000000..090ad14 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/AccumulatePreTreatment.java @@ -0,0 +1,28 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.pretreatment.CalculateUtils; +import com.cbsd.client.pretreatment.IPreTreatmentInterface; + +import java.util.Map; + +/** + * 累加预处理 + */ +public class AccumulatePreTreatment implements IPreTreatmentInterface { + + @Override + public String pretreatment(String command, int repeatCount, int hitCount, String latestHitTime, Map dataMap, CalculateUtils calculateUtils) { + String resultCommand = ""; + if (hitCount < 10) { + resultCommand += "0" + hitCount; + } else { + resultCommand = String.valueOf(hitCount); + } + return resultCommand; + } + + @Override + public String comment() { + return "根据命中次数累加拼接"; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/CrcCheck.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/CrcCheck.java new file mode 100644 index 0000000..965ae24 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/CrcCheck.java @@ -0,0 +1,31 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.verification.IVerificationInterface; +import com.cbsd.client.verification.VerifyUtils; +import org.springframework.stereotype.Component; + +/** + * CRC校验 + */ +@Component +public class CrcCheck implements IVerificationInterface { + + @Override + public boolean verification(byte[] data, byte[] verify, VerifyUtils verifyUtils) { + try { + byte[] verifyCRC = new byte[data.length + verify.length]; + System.arraycopy(data, 0, verifyCRC, 0, data.length); + System.arraycopy(verify, 0, verifyCRC, data.length, verify.length); + return verifyUtils.verifyCRC(verifyCRC); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + @Override + public String comment() { + return ""; + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/DigitalParser.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/DigitalParser.java new file mode 100644 index 0000000..b52b24d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/DigitalParser.java @@ -0,0 +1,50 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.parse.IParseDataInterface; + +public class DigitalParser implements IParseDataInterface { + @Override + public String parsePhysicalFromData(int sn, byte[] sourceData, byte[] fieldData, String endian, String binStr) { + + StringBuilder result = new StringBuilder(); + if (fieldData.length == 1) { + byte b = fieldData[0]; + String binary = byteToBinary(b); + for (int i = 0; i < binary.length(); i++) { + result.append("通道:"); + result.append(i); + if (binary.charAt(i) == '1') { + result.append(" 高电平"); + } else { + result.append(" 低电平"); + } + result.append(","); + } + result.deleteCharAt(result.lastIndexOf(",")); + } + return result.toString(); + } + + @Override + public String comment() { + return "数字信号解析"; + } + + /** + * 将byte转换为8位二进制字符串 + * + * @param b 要转换的byte值 + * @return 8位二进制字符串 + */ + public static String byteToBinary(byte b) { + // 将byte转换为无符号整数,避免符号位扩展 + int unsignedByte = b & 0xFF; + // 转换为二进制字符串 + String binary = Integer.toBinaryString(unsignedByte); + // 确保结果是8位,不足的在前面补0 + while (binary.length() < 8) { + binary = "0" + binary; + } + return binary; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/ScriptManager.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/ScriptManager.java new file mode 100644 index 0000000..a09056e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/ScriptManager.java @@ -0,0 +1,501 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.branch.IBranchConditionInterface; +import com.cbsd.client.check.ICheckInterface; +import com.cbsd.client.parse.IParseDataInterface; +import com.cbsd.client.pretreatment.IPreTreatmentInterface; +import com.cbsd.client.verification.IVerificationInterface; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.service.JavaCompileService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.DynamicClassLoader; +import com.cbsd.universaltestsoftware_client.util.FileUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.InstructScriptVo; +import com.cbsd.universaltestsoftware_client.vo.ScriptTypeVo; +import com.cbsd.universaltestsoftware_client.vo.ScriptVo; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.system.ApplicationHome; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Component +public class ScriptManager { + + @Resource + private JavaCompileService javaCompileService; + + public enum ScriptType { + IVerificationInterface("verification", "校验脚本", ScriptTemplate.VERIFICATION), + IParseDataInterface("parse", "解析脚本", ScriptTemplate.PARSE), + IPreTreatmentInterface("pretreatment", "预处理脚本", ScriptTemplate.PRE_TREATMENT), + IBranchConditionInterface("branch", "分支条件脚本", ScriptTemplate.BRANCH_CONDITION), + ICheckInterface("check", "检查脚本", ScriptTemplate.CHECK); + + @Getter + private final String value; + + @Getter + private final String typeName; + + @Getter + private final String template; + + ScriptType(String value, String typeName, String template) { + this.value = value; + this.typeName = typeName; + this.template = template; + } + + public static List getValues() { + return Stream.of(values()).map(ScriptType::getValue).collect(Collectors.toList()); + } + } + + /** + * 脚本包名 + */ + @Value("${cbsd.scriptBasePackageName}") + private String scriptBasePackageName; + /** + * 脚本文件路径 + */ + @Value("${cbsd.scriptRootPath}") + private String scriptRootPath; + + + private String scriptRealRootPath; + + /** + * 脚本类型map + */ + public static final Map>> scriptTypeMap = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + String jarPath = getJarPath(); + if (!jarPath.endsWith(".jar")) { + scriptRealRootPath = scriptRootPath; + } else { + scriptRealRootPath = jarPath.substring(0, jarPath.lastIndexOf(File.separator)) + File.separator + "script"; + } + new Thread(this::compileAllScript).start(); + } + + public String getJarPath() { + // 获取当前应用的ApplicationHome对象 + ApplicationHome home = new ApplicationHome(getClass()); + // 获取JAR文件对象 + File jarFile = home.getSource(); + if (jarFile != null) { + // 返回JAR文件的绝对路径 + return jarFile.getAbsolutePath(); + } + // 如果不是在JAR包中运行(如IDE中),返回当前工作目录 + return System.getProperty("user.dir"); + } + + /** + * 重新编译所有脚本 + */ + public void recompileAllScript() { + scriptTypeMap.clear(); + new Thread(this::compileAllScript).start(); + } + + /** + * 编译脚本 + */ + private void compileAllScript() { + for (ScriptType scriptType : ScriptType.values()) { + File file = new File(scriptRealRootPath, scriptType.getValue()); + if (!file.exists()) { + file.mkdirs(); + } + List fileList = FileUtils.getDirectoryFiles(file.getPath()); + File classDic = new File(file.getPath(), "classes"); + if (!classDic.exists()) { + classDic.mkdirs(); + } + Map> scriptMap = new ConcurrentHashMap<>(); + for (File scriptFile : fileList) { + if (scriptFile.getPath().endsWith(".java")) { + try { + String srcPackageName = scriptBasePackageName + "." + scriptType.getValue(); + String loadClassName = javaCompileService.compileJavaSource(srcPackageName, classDic.getPath(), scriptFile.getPath(), scriptFile.getName().replace(".java", "")) + .replace(classDic.getPath() + File.separatorChar, "") + .replace(File.separatorChar, '.') + .replace(".class", ""); + DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(classDic.getPath()); + Class dynamicClass = dynamicClassLoader.loadClass(loadClassName); + scriptMap.put(dynamicClass.getSimpleName(), dynamicClass); + } catch (Exception e) { + e.printStackTrace(); + System.out.println(e.getMessage()); + } + } else { + System.out.println(scriptFile.getPath() + " 不是一个java文件, 不能编译成java脚本"); + } + } + scriptTypeMap.put(scriptType.getValue(), scriptMap); + } + } + + /** + * 编译脚本文件 + * + * @param scriptType 脚本类型 + * @param scriptFile 脚本文件 + */ + public void compileScriptFile(String scriptType, File scriptFile) { + File scriptTypeDic = new File(scriptRealRootPath, scriptType); + if (!scriptTypeDic.exists()) { + scriptTypeDic.mkdirs(); + } + File classDic = new File(scriptTypeDic.getPath(), "classes"); + if (!classDic.exists()) { + classDic.mkdirs(); + } + if (scriptFile.getPath().endsWith(".java")) { + try { + Map> scriptMap = scriptTypeMap.get(scriptType); + if (scriptMap == null) { + scriptMap = new ConcurrentHashMap<>(); + } + String srcPackageName = scriptBasePackageName + "." + scriptType; + String loadClassName = javaCompileService.compileJavaSource(srcPackageName, classDic.getPath(), scriptFile.getPath(), scriptFile.getName().replace(".java", "")) + .replace(classDic.getPath() + File.separatorChar, "") + .replace(File.separatorChar, '.') + .replace(".class", ""); + DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(classDic.getPath()); + Class dynamicClass = dynamicClassLoader.loadClass(loadClassName); + scriptMap.put(dynamicClass.getSimpleName(), dynamicClass); + + scriptTypeMap.put(scriptType, scriptMap); + } catch (Exception e) { + throw new BaseException(e.getMessage()); + } + } + } + + /** + * 创建脚本文件 + * + * @param scriptType 脚本类型 + * @param scriptContent 脚本内容 + */ + public void createScriptFile(String scriptType, String scriptContent) { + String className = scriptContent.substring(scriptContent.indexOf("public class") + 12, scriptContent.indexOf("implements")).trim(); + File scriptTypeDic = new File(scriptRealRootPath, scriptType); + if (!scriptTypeDic.exists()) { + scriptTypeDic.mkdirs(); + } + File scriptFile = new File(scriptTypeDic, className + ".java"); + try { + Files.write(Paths.get(scriptFile.getPath()), scriptContent.getBytes(StandardCharsets.UTF_8)); + compileScriptFile(scriptType, scriptFile); + } catch (Exception e) { + scriptFile.delete(); + throw new BaseException(e.getMessage()); + } + } + + /** + * 删除脚本文件 + * + * @param scriptType 脚本类型 + * @param scriptFile 脚本文件 + */ + public void deleteScriptFile(String scriptType, String scriptFile) { + File scriptTypeDic = new File(scriptRealRootPath, scriptType); + if (scriptTypeDic.exists()) { + File existsFile = new File(scriptTypeDic, scriptFile); + if (existsFile.exists()) { + existsFile.delete(); + } + } + } + + /** + * 获取脚本类 + * + * @param scriptType 脚本类型 + * @param scriptName 脚本名称 + */ + public static Class getScriptClass(String scriptType, String scriptName) { + if (scriptTypeMap.get(scriptType) != null) { + return scriptTypeMap.get(scriptType).get(scriptName); + } else { + return null; + } + } + + /** + * 获取脚本类型列表 + */ + public List getScriptTypeList() { + List scriptTypeVoList = new ArrayList<>(); + for (ScriptType scriptType : ScriptType.values()) { + ScriptTypeVo scriptTypeVo = new ScriptTypeVo(); + scriptTypeVo.setScriptType(scriptType.getValue()); + scriptTypeVo.setScriptTypeName(scriptType.getTypeName()); + scriptTypeVo.setScriptTemplateContent(scriptType.getTemplate()); + scriptTypeVo.setScriptDirectoryPath(scriptRealRootPath + File.separator + scriptType.getValue()); + scriptTypeVoList.add(scriptTypeVo); + } + return scriptTypeVoList; + } + + /** + * 获取脚本列表 + * + * @param scriptType 脚本类型 + */ + public List getScriptList(String scriptType) { + List scriptVoList = new ArrayList<>(); + File scriptTypeDic = new File(scriptRealRootPath, scriptType); + if (scriptTypeDic.exists()) { + List fileList = FileUtils.getDirectoryFiles(scriptTypeDic.getPath()); + fileList.sort((o1, o2) -> { + if (o1.lastModified() > o2.lastModified()){ + return -1; + } else if (o1.lastModified() < o2.lastModified()){ + return 1; + } + return 0; + }); + + Map> scriptMap = scriptTypeMap.get(scriptType); + for (File file : fileList) { + ScriptVo scriptVo = new ScriptVo(); + scriptVo.setFilePath(file.getPath()); + scriptVo.setFileName(file.getName()); + scriptVo.setScriptType(scriptType); + scriptVo.setLastModifiedTime(DateUtils.timestampToDateString(file.lastModified())); + + String scriptName = file.getName().replace(".java", ""); + + if (scriptMap != null) { + Class cls = scriptMap.get(scriptName); + if (cls != null) { + //已编译 + scriptVo.setCompileStatus(1); + scriptVo.setScriptName(scriptName); + if (scriptType.equalsIgnoreCase(ScriptType.IVerificationInterface.getValue())) { + try { + IVerificationInterface checkInterface = (IVerificationInterface) getScriptClass(ScriptType.IVerificationInterface.getValue(), scriptName).newInstance(); + scriptVo.setScriptComment(checkInterface.comment()); + } catch (InstantiationException | IllegalAccessException ignored) { + scriptVo.setCompileStatus(0); + } + } else if (scriptType.equalsIgnoreCase(ScriptType.IParseDataInterface.getValue())) { + try { + IParseDataInterface parserDataInterface = (IParseDataInterface) getScriptClass(ScriptType.IParseDataInterface.getValue(), scriptName).newInstance(); + scriptVo.setScriptComment(parserDataInterface.comment()); + } catch (InstantiationException | IllegalAccessException ignored) { + scriptVo.setCompileStatus(0); + } + } else if (scriptType.equalsIgnoreCase(ScriptType.IPreTreatmentInterface.getValue())) { + try { + IPreTreatmentInterface preTreatmentInterface = (IPreTreatmentInterface) getScriptClass(ScriptType.IPreTreatmentInterface.getValue(), scriptName).newInstance(); + scriptVo.setScriptComment(preTreatmentInterface.comment()); + } catch (InstantiationException | IllegalAccessException ignored) { + scriptVo.setCompileStatus(0); + } + } else if (scriptType.equalsIgnoreCase(ScriptType.IBranchConditionInterface.getValue())) { + try { + IBranchConditionInterface branchConditionInterface = (IBranchConditionInterface) getScriptClass(ScriptType.IBranchConditionInterface.getValue(), scriptName).newInstance(); + scriptVo.setScriptComment(branchConditionInterface.comment()); + } catch (InstantiationException | IllegalAccessException ignored) { + scriptVo.setCompileStatus(0); + } + } else if (scriptType.equalsIgnoreCase(ScriptType.ICheckInterface.getValue())) { + try { + ICheckInterface checkInterface = (ICheckInterface) getScriptClass(ScriptType.ICheckInterface.getValue(), scriptName).newInstance(); + scriptVo.setScriptComment(checkInterface.comment()); + } catch (InstantiationException | IllegalAccessException ignored) { + scriptVo.setCompileStatus(0); + } + } + } else { + //未编译 + scriptVo.setCompileStatus(0); + } + } + scriptVoList.add(scriptVo); + } + } + return scriptVoList; + } + + /** + * 编译脚本 + * + * @param scriptType 脚本类型 + * @param scriptFileName 脚本文件名 + */ + public Result compileScript(String scriptType, String scriptFileName) { + File scriptTypeDic = new File(scriptRealRootPath, scriptType); + File scriptFile = new File(scriptTypeDic, scriptFileName); + if (!scriptFile.exists()) { + return Result.error("脚本文件不存在"); + } + try { + compileScriptFile(scriptType, scriptFile); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + return Result.success(); + } + + /** + * 删除脚本 + * + * @param scriptType 脚本类型 + * @param scriptFileName 脚本文件名 + * @param scriptName 脚本名称 + */ + public void deleteScript(String scriptType, String scriptFileName, String scriptName) { + File scriptTypeDic = new File(scriptRealRootPath, scriptType); + File scriptFile = new File(scriptTypeDic, scriptFileName); + if (scriptFile.exists()) { + //删除脚本java文件 + scriptFile.delete(); + } + if (StringUtils.isNotBlank(scriptName)) { + //删除脚本class文件 + File scriptClassDic = new File(scriptRealRootPath, scriptType + File.separator + "classes" + + File.separator + "com" + File.separator + "cbsd" + File.separator + "client" + File.separator + scriptType); + File scriptClassFile = new File(scriptClassDic, scriptName + ".class"); + if (scriptClassFile.exists()) { + scriptClassFile.delete(); + } + + if (scriptTypeMap.get(scriptType) != null) { + scriptTypeMap.get(scriptType).remove(scriptName); + } + } + } + + /** + * 上传脚本文件 + * + * @param scriptType 脚本类型 + * @param file 文件 + */ + public InstructScriptVo uploadScript(String scriptType, MultipartFile file) { + if (StringUtils.isBlank(scriptType)) { + throw new NullPointerException("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(scriptType)) { + throw new BaseException("脚本类型错误"); + } + if (file == null) { + throw new NullPointerException("文件不能为空"); + } + String fileName = file.getOriginalFilename(); + if (StringUtils.isBlank(fileName)) { + throw new NullPointerException("文件名称不能为空"); + } + File parentFile = new File(scriptRealRootPath, scriptType); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + String suffix = fileName.substring(fileName.lastIndexOf(".")); + if (!".java".equals(suffix)) { + throw new BaseException("请上传Java文件(.java)"); + } + File existsFile = new File(parentFile, fileName); + if (!existsFile.exists()) { + try { + Path savePath = Paths.get(parentFile.getPath(), fileName); + Files.write(savePath, file.getBytes()); + //添加进脚本 + compileScriptFile(scriptType, savePath.toFile()); + } catch (Exception e) { + throw new BaseException(e.getMessage()); + } + } + InstructScriptVo vo = new InstructScriptVo(); + vo.setScriptType(scriptType); + vo.setScriptFile(fileName); + vo.setScriptName(fileName.replace(suffix, "")); + return vo; + } + + /** + * 保存脚本内容 + * + * @param instructId 指令ID + * @param scriptType 脚本类型 + * @return + */ + public InstructScriptVo saveScriptContent(String instructId, String scriptType, String scriptContent) { + if (StringUtils.isBlank(instructId)) { + throw new NullPointerException("指令ID不能为空"); + } + if (StringUtils.isBlank(scriptType)) { + throw new NullPointerException("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(scriptType)) { + throw new BaseException("脚本类型错误"); + } + File parentFile = new File(scriptRealRootPath, scriptType); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + String firstChar = scriptType.substring(0, 1); + String className = firstChar.toUpperCase() + scriptType.replaceFirst(firstChar, "") + "_" + instructId; + if (StringUtils.isNotBlank(scriptContent)) { + className = scriptContent.substring(scriptContent.indexOf("public class") + 12, scriptContent.indexOf("implements")).trim(); + } + File scriptFile = new File(parentFile, className + ".java"); + //脚本内容为空的情况下删除之前的文件 + if (scriptFile.exists()) { + scriptFile.delete(); + } + if (StringUtils.isNotBlank(scriptContent)) { + // 使用try-with-resources语法,自动关闭资源 + try (BufferedWriter writer = Files.newBufferedWriter( + Paths.get(scriptFile.getPath()), + StandardCharsets.UTF_8)) { + writer.write(scriptContent); + } catch (IOException e) { + scriptFile.delete(); + throw new BaseException(e.getMessage()); + } + try { + //添加进脚本 + compileScriptFile(scriptType, scriptFile); + } catch (Exception e) { + scriptFile.delete(); + throw new BaseException(e.getMessage()); + } + } + InstructScriptVo vo = new InstructScriptVo(); + vo.setScriptFile(scriptFile.getName()); + vo.setScriptType(scriptType); + vo.setScriptName(className); + return vo; + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/ScriptTemplate.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/ScriptTemplate.java new file mode 100644 index 0000000..c8f95ab --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/ScriptTemplate.java @@ -0,0 +1,183 @@ +package com.cbsd.universaltestsoftware_client.script; + +/** + * 脚本模板 + */ +public class ScriptTemplate { + /** + * 预处理脚本 + */ + public static final String PRE_TREATMENT = "package com.cbsd.client.pretreatment;\n" + + "\n" + + "import com.cbsd.client.pretreatment.CalculateUtils;\n" + + "import com.cbsd.client.pretreatment.IPreTreatmentInterface;\n" + + "\n" + + "import java.util.Map;\n" + + "\n" + + "public class 【类名-请修改】 implements IPreTreatmentInterface {\n" + + " \n" + + " /**\n" + + " * 预处理\n" + + " * @param command 指令内容\n" + + " * @param repeatCount 重复次数\n" + + " * @param hitCount 命中次数\n" + + " * @param latestHitTime 最后命中时间\n" + + " * @param dataMap 数据集合,key为协议字段属性名(label),value为物理值\n" + + " * @param calculateUtils 计算工具类\n" + + " * @return 结果\n" + + " */\n" + + " @Override\n" + + " public String pretreatment(String command, int repeatCount, int hitCount, String latestHitTime, Map dataMap, CalculateUtils calculateUtils) {\n" + + " return \"\";\n" + + " }\n" + + "\n" + + " /**\n" + + " * 描述\n" + + " *\n" + + " * @return 描述内容\n" + + " */\n" + + " @Override\n" + + " public String comment() {\n" + + " return \"\";\n" + + " }\n" + + "}\n"; + + /** + * 分支条件脚本 branchCondition 方法 + */ + public static final String BRANCH_CONDITION = "package com.cbsd.client.branch;\n" + + "\n" + + "import com.cbsd.client.branch.IBranchConditionInterface;\n" + + "\n" + + "import java.util.Map;\n" + + "\n" + + "public class 【类名-请修改】 implements IBranchConditionInterface {\n" + + "\n" + + " /**\n" + + " * 分支条件\n" + + " * @param originalCommand 原始指令数据\n" + + " * @param preTreatmentCommand 预处理指令数据,若无预处理,则为原始指令数据\n" + + " * @param repeatCount 重复次数\n" + + " * @param hitCount 命中次数\n" + + " * @param latestHitTime 最后命中时间\n" + + " * @param dataMap 数据集合,key为协议字段属性名(label),value为物理值\n" + + " * @return 需要跳转的指令名称,为空则为不跳转\n" + + " */\n" + + " @Override\n" + + " public String branchCondition(String originalCommand, String preTreatmentCommand, int repeatCount, int hitCount, String latestHitTime, Map dataMap) {\n" + + " return \"\";\n" + + " }\n" + + " \n" + + " /**\n" + + " * 描述\n" + + " *\n" + + " * @return 描述内容\n" + + " */\n" + + " @Override\n" + + " public String comment() {\n" + + " return \"\";\n" + + " }\n" + + "}\n"; + + /** + * 检查脚本 check 方法 + */ + public static final String CHECK = "package com.cbsd.client.check;\n" + + "\n" + + "import com.cbsd.client.check.ICheckInterface;\n" + + "\n" + + "import java.util.List;\n" + + "import java.util.Map;\n" + + "\n" + + "public class 【类名-请修改】 implements ICheckInterface {\n" + + "\n" + + " /**\n" + + " * 检查\n" + + " * @param originalCommand 原始指令数据\n" + + " * @param preTreatmentCommand 预处理指令数据,若无预处理,则为原始指令数据\n" + + " * @param repeatCount 重复次数\n" + + " * @param hitCount 命中次数\n" + + " * @param latestHitTime 最后命中时间\n" + + " * @param dataMap 数据集合,key为协议字段属性名(label),value为物理值\n" + + " * @return 日志信息列表\n" + + " */\n" + + " @Override\n" + + " public List check(String originalCommand, String preTreatmentCommand, int repeatCount, int hitCount, String latestHitTime, Map dataMap) {\n" + + " return null;\n" + + " }\n" + + "\n" + + " /**\n" + + " * 描述\n" + + " *\n" + + " * @return 描述内容\n" + + " */\n" + + " @Override\n" + + " public String comment() {\n" + + " return \"\";\n" + + " }\n" + + "}\n"; + + public static final String PARSE = "package com.cbsd.client.parse;\n" + + "\n" + + "import com.cbsd.client.parse.IParseDataInterface;\n" + + "\n" + + "public class 【类名-请修改】 implements IParseDataInterface {\n" + + "\n" + + " /**\n" + + " * 从数据解析物理值\n" + + " *\n" + + " * @param sn 当前字段序号\n" + + " * @param sourceData 源数据\n" + + " * @param fieldData 当前字段数据\n" + + " * @param endian BIG:大端序 LITTLE:小端序\n" + + " * @param binStr 二进制字符串\n" + + " * @return 解析后的数据值\n" + + " */\n" + + " @Override\n" + + " public String parsePhysicalFromData(int sn, byte[] sourceData, byte[] fieldData, String endian, String binStr) {\n" + + " return \"\";\n" + + " }\n" + + "\n" + + " /**\n" + + " * 描述\n" + + " *\n" + + " * @return 描述内容\n" + + " */\n" + + " @Override\n" + + " public String comment() {\n" + + " return \"\";\n" + + " }\n" + + "}\n"; + + public static final String VERIFICATION = "package com.cbsd.client.verification;\n" + + "\n" + + "import com.cbsd.client.verification.IVerificationInterface;\n" + + "import com.cbsd.client.verification.VerifyUtils;\n" + + "\n" + + "public class 【类名-请修改】 implements IVerificationInterface {\n" + + "\n" + + " /**\n" + + " * 校验\n" + + " *\n" + + " * @param data 校验数据\n" + + " * @param verify 校验位\n" + + " * @param verifyUtils 校验工具类\n" + + " * @return 校验结果\n" + + " */\n" + + " @Override\n" + + " public boolean verification(byte[] data, byte[] verify, VerifyUtils verifyUtils) {\n" + + " return false;\n" + + " }\n" + + "\n" + + " /**\n" + + " * 描述\n" + + " *\n" + + " * @return 描述内容\n" + + " */\n" + + " @Override\n" + + " public String comment() {\n" + + " return \"\";\n" + + " }\n" + + "}\n"; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/TemperatureParser.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/TemperatureParser.java new file mode 100644 index 0000000..e9de431 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/TemperatureParser.java @@ -0,0 +1,33 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.parse.IParseDataInterface; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class TemperatureParser implements IParseDataInterface { + + @Override + public String parsePhysicalFromData(int sn, byte[] sourceData, byte[] fieldData, String endian, String binStr) { + List serialPortDataHexList = new ArrayList<>(); + for (byte b : fieldData) { + int h = (b >>> 4) & 0xF; + int l = b & 0xF; + char ch = (char) ((h < 10) ? ('0' + h) : ('A' + h - 10)); + char cl = (char) ((l < 10) ? ('0' + l) : ('A' + l - 10)); + String sb = String.valueOf(ch) + cl; + serialPortDataHexList.add(sb); + } + //温度 + String temperatureChar = serialPortDataHexList.get(0) + serialPortDataHexList.get(1); + double temperature = Integer.parseInt(temperatureChar, 16) / 10.0; + return String.valueOf(temperature); + } + + @Override + public String comment() { + return ""; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/TestBranchCondition.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/TestBranchCondition.java new file mode 100644 index 0000000..bb543fe --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/TestBranchCondition.java @@ -0,0 +1,26 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.branch.IBranchConditionInterface; + +import java.util.Map; + +public class TestBranchCondition implements IBranchConditionInterface { + + @Override + public String branchCondition(String originalCommand, String preTreatmentCommand, int repeatCount, int hitCount, String latestHitTime, Map dataMap) { + String temperatureStr = dataMap.get("temperature"); + if (temperatureStr != null && !temperatureStr.isEmpty()){ + double temperature = Double.parseDouble(temperatureStr); + if (temperature > 25.5){ + //此处为指令名称 + return "打开降温指令"; + } + } + return null; + } + + @Override + public String comment() { + return "跳转指令5"; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/TestCheckScript.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/TestCheckScript.java new file mode 100644 index 0000000..e015adf --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/TestCheckScript.java @@ -0,0 +1,23 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.check.ICheckInterface; +import com.cbsd.universaltestsoftware_client.util.DateUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class TestCheckScript implements ICheckInterface { + + @Override + public List check(String originalCommand, String preTreatmentCommand, int repeatCount, int hitCount, String latestHitTime, Map dataMap) { + List list = new ArrayList<>(); + list.add(DateUtils.dateToString() + " 检查合格"); + return list; + } + + @Override + public String comment() { + return "打印检查日志"; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/TestScript.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/TestScript.java new file mode 100644 index 0000000..e13478b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/TestScript.java @@ -0,0 +1,8 @@ +package com.cbsd.universaltestsoftware_client.script; + +public class TestScript { + + public static void main(String[] args) { + System.out.println("测试脚本"); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/script/VoltageParser.java b/src/main/java/com/cbsd/universaltestsoftware_client/script/VoltageParser.java new file mode 100644 index 0000000..f0062c9 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/script/VoltageParser.java @@ -0,0 +1,46 @@ +package com.cbsd.universaltestsoftware_client.script; + +import com.cbsd.client.parse.IParseDataInterface; + +import java.nio.ByteBuffer; + +public class VoltageParser implements IParseDataInterface { + @Override + public String parsePhysicalFromData(int sn, byte[] sourceData, byte[] fieldData, String endian, String binStr) { + if (fieldData.length % 8 != 0){ + return ""; + } + double[] doubles = bytesToDoubles(fieldData); + if (doubles.length == 0) { + return ""; + } + return String.valueOf(doubles[0]); + } + + @Override + public String comment() { + return "ADDA卡获取电压0到电压31"; + } + + /** + * 将byte数组转换回double数组(还原原始数据) + * + * @param bytes 输入的byte数组 + * @return 转换后的double数组 + * @throws IllegalArgumentException 如果字节数组长度不是8的倍数 + */ + public static double[] bytesToDoubles(byte[] bytes) { + // 计算可以转换的double数量 + int doubleLength = bytes.length / 8; + // 创建ByteBuffer并放入字节数组 + ByteBuffer buffer = ByteBuffer.wrap(bytes); + double[] doubles = new double[doubleLength]; + + // 从缓冲区中读取所有double值 + for (int i = 0; i < doubleLength; i++) { + doubles[i] = buffer.getDouble(); + } + + return doubles; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/BoardService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/BoardService.java new file mode 100644 index 0000000..d7fc79b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/BoardService.java @@ -0,0 +1,48 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.dto.BoardTemperatureDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelPageDto; +import com.cbsd.universaltestsoftware_client.dto.PageBoard; +import com.cbsd.universaltestsoftware_client.entity.Board; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.entity.Channel; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + *

+ * 板卡 服务类 + *

+ * + * @author admin + * @since 2025-08-26 + */ +public interface BoardService { + + String pageList(PageBoard pageBoard); + + String deleteById(String serverId,String boardId); + + String getBoardType(String serverId); + + String saveOrUpdateBoard(Board board); + + String saveOrUpdateChannel(Channel channel); + + Result pageChannelList(ChannelPageDto channelPageDto); + + String deleteChannelById(String serverId, String channelId); + + String getChannelPath(String serverId,String boardId); + + String refressBoardInformation( String serverId); + + String getChannelTypeAll(String serverId,String boardTypeId); + + String getBoardTemperature(@RequestBody BoardTemperatureDto dto); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataHitService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataHitService.java new file mode 100644 index 0000000..77da26f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataHitService.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.ChannelDataHit; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author SanJie +* @description 针对表【channel_data_hit(通道协议命中次数)】的数据库操作Service +* @createDate 2026-01-23 16:24:09 +*/ +public interface ChannelDataHitService extends IService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataService.java new file mode 100644 index 0000000..9b3d307 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataService.java @@ -0,0 +1,34 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataHistoryDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataStatisticsDto; +import com.cbsd.universaltestsoftware_client.dto.PageChannelDataDto; +import com.cbsd.universaltestsoftware_client.entity.ChannelData; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.ChannelDataStatisticsVo; + +import java.util.List; + +/** +* @author SanJie +* @description 针对表【channel_data(解析的数据)】的数据库操作Service +* @createDate 2025-09-09 09:53:44 +*/ +public interface ChannelDataService extends IService { + void start(PageChannelDataDto pageChannelDataDto); + + void stop(String dataBoardId); + + void updateData(PageChannelDataDto pageChannelDataDto); + + String channelDataQuery(ChannelDataHistoryDto channelDataHistoryDto); + + String channelDataDelete(ChannelDataHistoryDto channelDataHistoryDto); + + Result> getChannelDataStatistics(ChannelDataStatisticsDto dto); + + Result clearChannelDataStatistics(ChannelDataStatisticsDto dto); + + void clearChannelData(); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataTrafficService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataTrafficService.java new file mode 100644 index 0000000..f1cd28b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ChannelDataTrafficService.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.ChannelDataTraffic; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author SanJie +* @description 针对表【channel_data_traffic(通道流量统计)】的数据库操作Service +* @createDate 2026-01-23 10:07:19 +*/ +public interface ChannelDataTrafficService extends IService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/DataBoardChannelService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/DataBoardChannelService.java new file mode 100644 index 0000000..5d8127f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/DataBoardChannelService.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author admin + * @since 2025-09-10 + */ +public interface DataBoardChannelService extends IService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/DataBoardService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/DataBoardService.java new file mode 100644 index 0000000..16a20a4 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/DataBoardService.java @@ -0,0 +1,39 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.DownloadDto; +import com.cbsd.universaltestsoftware_client.dto.ExportDataBoardDto; +import com.cbsd.universaltestsoftware_client.dto.InsertDataBoardDto; +import com.cbsd.universaltestsoftware_client.entity.DataBoard; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import com.cbsd.universaltestsoftware_client.entity.Download; +import com.cbsd.universaltestsoftware_client.util.Result; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + *

+ * 数据看板 服务类 + *

+ * + * @author admin + * @since 2025-09-10 + */ +public interface DataBoardService extends IService { + + void saveOrUpdateDataBoard(InsertDataBoardDto insertDataBoardDto) throws Exception; + + void removeDataBoardById(String dataBoardId); + + List listDataBoard(Integer dataType); + + void updateDataBoardById(DataBoardChannel dataBoardChannel); + + Result exportDataBoard(ExportDataBoardDto exportDataBoardDto, HttpServletResponse response); + + void clear(ExportDataBoardDto exportDataBoardDto); + + Result> exportHistory(DownloadDto downloadDto); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/DownloadService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/DownloadService.java new file mode 100644 index 0000000..14b3e85 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/DownloadService.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.Download; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author admin + * @since 2026-02-03 + */ +public interface DownloadService extends IService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/IBootLogService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/IBootLogService.java new file mode 100644 index 0000000..ed51d75 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/IBootLogService.java @@ -0,0 +1,24 @@ +package com.cbsd.universaltestsoftware_client.service; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.dto.BootLogDeleteDto; +import com.cbsd.universaltestsoftware_client.dto.BootLogPageDto; +import com.cbsd.universaltestsoftware_client.entity.BootLog; +import com.cbsd.universaltestsoftware_client.util.Result; + +/** + *

+ * 服务类 + *

+ * + * @author wt + * @since 2025-09-03 + */ +public interface IBootLogService extends IService { + + Result> bootLogPage(BootLogPageDto bootLogPageDto); + + Result bootLogDelete(BootLogDeleteDto bootLogDeleteDto); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/InstructService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/InstructService.java new file mode 100644 index 0000000..0308fea --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/InstructService.java @@ -0,0 +1,72 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.*; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.InstructScriptVo; +import com.cbsd.universaltestsoftware_client.vo.ServerListVo; +import com.cbsd.universaltestsoftware_client.websocket.command.CommandRunMessage; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author admin + * @since 2025-09-01 + */ +public interface InstructService extends IService { + + Page pageList(InstructPageDto instructPageDto); + +// Result execute(String instructId, CommandRunMessage runMessage); + + Result> getChannelList(String keyword); + + String getChannelFunctionName(String channelId, String serverId); + + Result saveOrUpdateInstructGroup(InstructGroupDto instructGroupDto); + + Result saveOrUpdateInstruct(Instruct instruct); + + /** + * 上移:与前一个兄弟交换 sort + */ + Result moveUp(String instructId); + + /** + * 下移:与后一个兄弟交换 sort + */ + Result moveDown(String instructId); + + Result deleteInstructById(InstructDeleteDto dto); + + String getBoardDriverExtraData(String channelId, String serverId); + + Result> getInstructProtocolParam(String instructId); + + void createGroupFromList(CreateGroupDTO dto); + + Result batchChangeParam(BatchChangeInstructParamDto dto); + + void batchCopyBelow(BatchCopyBelowDTO dto); + + void batchMoveBelow(BatchMoveBelowDTO dto); + + Result uploadCommandFile(String instructId, MultipartFile file); + + void uploadScript(String instructId, String scriptType, String scriptName); + + void uploadScriptContent(String instructId, String scriptType, String scriptFileName, String scriptContent); + + Result deleteInstructFile(InstructDeleteFileDto dto); + + String getInstructDataRemark(String instructId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/InvocationService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/InvocationService.java new file mode 100644 index 0000000..f64ccf6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/InvocationService.java @@ -0,0 +1,34 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.dto.InvocationPageDto; +import com.cbsd.universaltestsoftware_client.dto.InvocationSortDto; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + *

+ * 用列 服务类 + *

+ * + * @author admin + * @since 2025-09-01 + */ +public interface InvocationService extends IService { + + Page pageList(InvocationPageDto invocationPageDto); + + Result sort(InvocationSortDto dto); + + + void exportInvocation(String invocationId, HttpServletResponse response) throws IOException; + + void importXml(MultipartFile file,String schemeId)throws Exception; + + Result deleteById(String invocationId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/JavaCompileService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/JavaCompileService.java new file mode 100644 index 0000000..3f76396 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/JavaCompileService.java @@ -0,0 +1,297 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.DynamicClassBootstrap; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.util.DynamicClassLoader; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.system.ApplicationHome; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.tools.*; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class JavaCompileService { + + // 编译输出目录 + private final String OUTPUT_DIR; + private final String scriptBasePackageName; + // 用于匹配包名声明的正则表达式 + private static final Pattern PACKAGE_PATTERN = Pattern.compile("^\\s*package\\s+([\\w.]+)\\s*;\\s*$", Pattern.MULTILINE); + + public JavaCompileService(@Value("${cbsd.uploadfilesrootpath}") String outputDir, + @Value("${cbsd.scriptBasePackageName}") String scriptBasePackageName) { + this.OUTPUT_DIR = outputDir; + this.scriptBasePackageName = scriptBasePackageName; + + // 初始化输出目录 + File dir = new File(outputDir); + if (!dir.exists()) { + dir.mkdirs(); + } + } + + /** + * 编译上传的Java文件 + */ + public Result compileJavaFile(MultipartFile file) throws IOException { + // 验证文件是否为Java文件 + String fileName = file.getOriginalFilename(); + if (fileName == null || !fileName.endsWith(".java")) { + return Result.error("请上传Java文件(.java)"); + } + + // 创建临时目录存储上传的Java文件 + String tempDir = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID(); + new File(tempDir).mkdirs(); + + // 获取类名 + String className = fileName.substring(0, fileName.lastIndexOf(".")); + + // 保存上传的文件到临时目录 + Path javaFilePath = Paths.get(tempDir, fileName); + Files.write(javaFilePath, file.getBytes()); + + // 读取文件内容,校验包名 + String fileContent = new String(file.getBytes(), StandardCharsets.UTF_8); + Result packageCheckResult = validatePackageName(fileContent); + if (packageCheckResult.getCode() != 1) { + deleteDirectory(new File(tempDir)); + return packageCheckResult; + } + + String fullClassName = scriptBasePackageName + "." + className; + + + + // 清理临时文件 + deleteDirectory(new File(tempDir)); + + // 加载类到缓存 + + try { + DynamicClassLoader classLoader = new DynamicClassLoader(OUTPUT_DIR); + Class dynamicClass = classLoader.loadClass(fullClassName); + DynamicClassBootstrap.CLAZZ_CACHE.put(fullClassName, dynamicClass); + } catch (Exception e) { + e.printStackTrace(); + return Result.error("类加载失败: " + e.getMessage()); + } + + try { + // 编译Java文件 + String classResult = compileJavaSource(scriptBasePackageName, OUTPUT_DIR, javaFilePath.toString(), className); + return Result.success(classResult); + }catch (Exception e){ + return Result.error(e.getMessage()); + } + } + + /** + * 编译Java代码字符串 + */ + public Result compileJavaCode(String className, String code) throws IOException { + // 校验包名 + Result packageCheckResult = validatePackageName(code); + if (packageCheckResult.getCode() != 1) { + return packageCheckResult; + } + + // 创建临时目录 + String tempDir = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID(); + new File(tempDir).mkdirs(); + + // 创建Java文件 + String fileName = className + ".java"; + Path javaFilePath = Paths.get(tempDir, fileName); + Files.write(javaFilePath, code.getBytes(StandardCharsets.UTF_8)); + + // 编译Java文件 + String fullClassName = scriptBasePackageName + "." + className; + + // 加载类到缓存 + try { + DynamicClassLoader classLoader = new DynamicClassLoader(OUTPUT_DIR); + Class dynamicClass = classLoader.loadClass(fullClassName); + DynamicClassBootstrap.CLAZZ_CACHE.put(fullClassName, dynamicClass); + } catch (Exception e) { + e.printStackTrace(); + return Result.error("类加载失败: " + e.getMessage()); + } + + // 清理临时文件 + deleteDirectory(new File(tempDir)); + + try { + // 编译Java文件 + String classResult = compileJavaSource(scriptBasePackageName, OUTPUT_DIR, javaFilePath.toString(), className); + return Result.success(classResult); + }catch (Exception e){ + return Result.error(e.getMessage()); + } + } + + /** + * 校验Java代码中的包名是否与配置的一致 + */ + private Result validatePackageName(String javaCode) { + Matcher matcher = PACKAGE_PATTERN.matcher(javaCode); + + if (!matcher.find()) { + return Result.error("Java代码中未声明包名,请添加包名声明: package " + scriptBasePackageName + ";"); + } + + String codePackageName = matcher.group(1).trim(); + if (!codePackageName.equals(scriptBasePackageName)) { + return Result.error("包名不匹配,必须使用指定包名: " + scriptBasePackageName + ",但实际使用了: " + codePackageName); + } + + return Result.success("包名校验通过"); + } + + /** + * 实际执行Java编译的方法 + */ + public String compileJavaSource(String srcPackageName, String outPath, String filePath, String className) { + // 获取系统Java编译器 + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new BaseException("未找到Java编译器,请确保使用JDK而非JRE运行程序"); + } + + // 创建诊断收集器,用于捕获编译错误 + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + // 创建文件管理器 + StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null); + + // 要编译的文件 + File javaFile = new File(filePath); + List files = Collections.singletonList(javaFile); + + // 将文件转换为JavaFileObject + Iterable compilationUnits = + fileManager.getJavaFileObjectsFromFiles(files); + + // 编译选项,指定输出目录 + List options = new ArrayList<>(); + options.add("-d"); + options.add(outPath); + options.add("-encoding"); + options.add("UTF-8"); + + String jarPath = getJarPath(); + if (jarPath.endsWith(".jar")){ + // 设置类路径,关键步骤:添加所有依赖的JAR包 + options.add("-classpath"); + String classPath = buildClassPath(jarPath.substring(0, jarPath.lastIndexOf(File.separator))); + options.add(classPath); + + } + + // 编译任务 + JavaCompiler.CompilationTask task = compiler.getTask( + null, // 输出流 + fileManager, // 文件管理器 + diagnostics, // 诊断收集器 + options, // 编译选项 + null, // 要处理的类 + compilationUnits // 要编译的源文件 + ); + + // 执行编译任务 + boolean success = task.call(); + + try { + fileManager.close(); + } catch (IOException e) { + throw new BaseException("编译过程出错: " + e.getMessage()); + } + + // 收集错误信息 + if (!success) { + StringBuilder errorMsg = new StringBuilder(); + for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { + errorMsg + .append(filePath) + .append(" ") + .append(String.format("错误位置: 第%d行, 第%d列 - %s%n", + diagnostic.getLineNumber(), + diagnostic.getColumnNumber(), + diagnostic.getMessage(null))); + } + + throw new BaseException(errorMsg.toString()); + } + + // 编译成功,返回class文件路径 + String[] pkgParts = srcPackageName.split("\\."); + Path classFile = Paths.get(outPath, pkgParts).resolve(className + ".class"); + + return classFile.toAbsolutePath().toString(); + } + + // 构建类路径的工具方法 + private String buildClassPath(String jarPath) { + // 系统默认类路径 + StringBuilder classpath = new StringBuilder(System.getProperty("java.class.path")); + + // 依赖的JAR文件目录 + File libDir = new File(jarPath, "libs" + File.separator + "lib"); + if (libDir.exists() && libDir.isDirectory()) { + File[] jars = libDir.listFiles((dir, name) -> name.endsWith(".jar")); + if (jars != null) { + for (File jar : jars) { + classpath.append(File.pathSeparator).append(jar.getAbsolutePath()); + } + } + } + // 额外的class文件目录 + classpath.append(File.pathSeparator).append(jarPath + File.separator + "libs" + File.separator + "classes" + File.separator); + + return classpath.toString(); + } + + /** + * 递归删除目录 + */ + private boolean deleteDirectory(File directory) { + if (directory.exists()) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + file.delete(); + } + } + } + } + return directory.delete(); + } + + public String getJarPath() { + // 获取当前应用的ApplicationHome对象 + ApplicationHome home = new ApplicationHome(getClass()); + // 获取JAR文件对象 + File jarFile = home.getSource(); + if (jarFile != null) { + // 返回JAR文件的绝对路径 + return jarFile.getAbsolutePath(); + } + // 如果不是在JAR包中运行(如IDE中),返回当前工作目录 + return System.getProperty("user.dir"); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ParserProtocolService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ParserProtocolService.java new file mode 100644 index 0000000..3ce3a3f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ParserProtocolService.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.ParserProtocol; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author SanJie +* @description 针对表【parser_protocol(解析器协议)】的数据库操作Service +* @createDate 2025-09-04 15:56:52 +*/ +public interface ParserProtocolService extends IService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ParserService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ParserService.java new file mode 100644 index 0000000..617f42c --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ParserService.java @@ -0,0 +1,28 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.Parser; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.entity.ParserProtocol; +import com.cbsd.universaltestsoftware_client.util.Result; + +import java.util.List; + +/** +* @author SanJie +* @description 针对表【parser(解析器)】的数据库操作Service +* @createDate 2025-09-04 15:55:48 +*/ +public interface ParserService extends IService { + + Result> getParserList(String channelId); + + Result saveOrUpdateParser(Parser parser); + + Result deleteParserById(String parserId); + + Result addProtocol(List parserProtocol); + + Result> getProtocolList(String parserId); + + Result deleteParserProtocolById(String parserProtocolId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ProtocolService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ProtocolService.java new file mode 100644 index 0000000..43c6fab --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ProtocolService.java @@ -0,0 +1,29 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.dto.ProtocolPageDto; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.ProtocolSaveVo; +import org.springframework.web.multipart.MultipartFile; + +/** + *

+ * 协议 服务类 + *

+ * + * @author admin + * @since 2025-09-02 + */ +public interface ProtocolService extends IService { + + Result> pageListProtocol(ProtocolPageDto dto); + + Result deleteProtocolById(String protocolId); + + Result saveOrUpdateProtocol(Protocol protocol); + + Result importProtocol(MultipartFile file); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/SchemeService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/SchemeService.java new file mode 100644 index 0000000..e85680a --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/SchemeService.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Scheme; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.util.Result; + +/** + *

+ * 方案 服务类 + *

+ * + * @author admin + * @since 2025-09-01 + */ +public interface SchemeService extends IService { + + Result> pageList(BasePage basePage); + + Result deleteScheme(String schemeId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/SensorEquipmentService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/SensorEquipmentService.java new file mode 100644 index 0000000..6557b52 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/SensorEquipmentService.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.dto.SensorEquipmentDto; +import com.cbsd.universaltestsoftware_client.dto.SensorEquipmentPageDto; +import com.cbsd.universaltestsoftware_client.entity.SensorEquipment; +import com.cbsd.universaltestsoftware_client.util.Result; + +import java.util.List; + +public interface SensorEquipmentService extends IService { + + Result> pageList(SensorEquipmentPageDto dto); + + Result> getAllList(String channelId); + + Result addOrUpdate(SensorEquipmentDto dto); + + Result delete(String equipmentId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/ServeService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/ServeService.java new file mode 100644 index 0000000..c7ec512 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/ServeService.java @@ -0,0 +1,28 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.dto.PageServerInfoDto; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.util.Result; + +/** + *

+ * 服务端 服务类 + *

+ * + * @author admin + * @since 2025-08-26 + */ +public interface ServeService extends IService { + + Result> pageList(PageServerInfoDto basePage); + + Result deleteById(String serverId); + + Result connectOrDisconnect(String serverId); + + + Result disconnectServer(String serverId); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/SystemConfigService.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/SystemConfigService.java new file mode 100644 index 0000000..f253b2d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/SystemConfigService.java @@ -0,0 +1,23 @@ +package com.cbsd.universaltestsoftware_client.service; + +import com.cbsd.universaltestsoftware_client.entity.SystemConfig; +import com.baomidou.mybatisplus.extension.service.IService; +import com.cbsd.universaltestsoftware_client.util.Result; + +import java.util.List; + +/** +* @author SanJie +* @description 针对表【system_config(系统配置(仅类型和内容))】的数据库操作Service +* @createDate 2025-09-19 11:18:42 +*/ +public interface SystemConfigService extends IService { + + Result> getShowColumnList(); + + Result setShowColumnList(List columnList); + + String getSystemConfig(int type); + + void setSystemConfig(int type, String value); +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/BoardServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/BoardServiceImpl.java new file mode 100644 index 0000000..6bda39f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/BoardServiceImpl.java @@ -0,0 +1,273 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.dto.BoardTemperatureDto; +import com.cbsd.universaltestsoftware_client.dto.BoardTemperatureServerDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelPageDto; +import com.cbsd.universaltestsoftware_client.dto.PageBoard; +import com.cbsd.universaltestsoftware_client.entity.*; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataTrafficMapper; +import com.cbsd.universaltestsoftware_client.mapper.ParserMapper; +import com.cbsd.universaltestsoftware_client.mapper.ServerMapper; +import com.cbsd.universaltestsoftware_client.service.BoardService; +import com.cbsd.universaltestsoftware_client.util.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 板卡服务实现类 + * 提供板卡与通道的远程增删改查功能 + * + * @author admin + * @since 2025-08-26 + */ +@Slf4j +@Service +public class BoardServiceImpl implements BoardService { + + @Resource + private ServerMapper serverMapper; + + @Resource + private ParserMapper parserMapper; + + @Resource + private ChannelDataTrafficMapper channelDataTrafficMapper; + + /** + * 构造服务端请求基础URL + */ + private String buildUrl(ServerInfo server, String path) { + return String.format("http://%s:%s%s", server.getIpAddress(), server.getHttpPort(), path); + } + + /** + * 检查服务端是否可连接 + */ + private void checkServerConnected(ServerInfo server) { + if (server == null) { + throw new RuntimeException("服务端不存在"); + } + if (server.getConnectStatus() == 0) { + throw new RuntimeException("服务端未连接"); + } + } + + /** + * 通用POST请求封装 + */ + private String postToServer(ServerInfo server, String path, Object body) { + try { + String url = buildUrl(server, path); + String result = HttpRequest.post(url) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(body)) + .execute() + .body(); + if (StringUtils.isBlank(result)) { + throw new RuntimeException("服务端返回为空"); + } + return result; + } catch (Exception e) { + log.error("请求服务端失败, path={}, error={}", path, e.getMessage()); + throw new RuntimeException("服务端连接失败"); + } + } + + /** + * 通用GET请求封装 + */ + private String getFromServer(ServerInfo server, String path, Map params) { + try { + String url = buildUrl(server, path); + String result = HttpUtil.get(url, params); + if (StringUtils.isBlank(result)) { + throw new RuntimeException("服务端返回为空"); + } + return result; + } catch (Exception e) { + log.error("请求服务端失败, path={}, error={}", path, e.getMessage()); + throw new RuntimeException("服务端连接失败"); + } + } + + /** + * 分页查询板卡列表 + */ + @Override + public String pageList(PageBoard pageBoard) { + ServerInfo server = serverMapper.selectById(pageBoard.getServerId()); + if (server == null) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + if (server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + return postToServer(server, "/universaltestsoftware_server_api/outer/client/pageListBoard", pageBoard); + } + + /** + * 删除板卡 + */ + @Override + public String deleteById(String serverId, String boardId) { + ServerInfo server = serverMapper.selectById(serverId); + checkServerConnected(server); + + Map params = Collections.singletonMap("boardId", boardId); + return getFromServer(server, "/universaltestsoftware_server_api/outer/client/deleteBoardById", params); + } + + /** + * 获取所有板卡类型 + */ + @Override + public String getBoardType(String serverId) { + ServerInfo server = serverMapper.selectById(serverId); + if (server == null || server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + return getFromServer(server, "/universaltestsoftware_server_api/outer/client/getBoardTypeAll", new HashMap<>()); + } + + /** + * 保存或更新板卡 + */ + @Override + public String saveOrUpdateBoard(Board board) { + ServerInfo server = serverMapper.selectById(board.getServerId()); + checkServerConnected(server); + return postToServer(server, "/universaltestsoftware_server_api/outer/client/saveOrUpdateBoard", board); + } + + /** + * 保存或更新通道 + */ + @Override + public String saveOrUpdateChannel(Channel channel) { + ServerInfo server = serverMapper.selectById(channel.getServerId()); + checkServerConnected(server); + return postToServer(server, "/universaltestsoftware_server_api/outer/client/channelInsertOrUpdate", channel); + } + + /** + * 分页查询通道列表,并补充解析数量 + */ + @Override + public Result pageChannelList(ChannelPageDto dto) { + ServerInfo server = serverMapper.selectById(dto.getServerId()); + if (server == null || server.getConnectStatus() == 0) { + return Result.success(); + } + + try { + String result = postToServer(server, "/universaltestsoftware_server_api/outer/client/channelPage", dto); + Result resultObj = JSON.parseObject(result, Result.class); + if (resultObj.getCode() == 1) { + BasePage basePage = JSON.parseObject(JSON.toJSONString(resultObj.getData()), BasePage.class); + List channels = JSON.parseArray(JSON.toJSONString(basePage.getRecords()), Channel.class); + + List channelDataTrafficList = channelDataTrafficMapper.selectList(Wrappers.emptyWrapper()); + Map channelDataTrafficMap = null; + if (channelDataTrafficList != null && !channelDataTrafficList.isEmpty()){ + channelDataTrafficMap = channelDataTrafficList.stream() + .collect(Collectors.toMap(ChannelDataTraffic::getChannelId, channelDataTraffic -> channelDataTraffic)); + } + // 补充解析数量 + for (Channel channel : channels) { + channel.setServerId(server.getServerId()); + long count = parserMapper.selectCount(new LambdaQueryWrapper() + .eq(Parser::getChannelId, channel.getChannelId())); + channel.setParsingQuantity((int) count); + + if (channelDataTrafficMap != null && channelDataTrafficMap.get(channel.getChannelId()) != null){ + channel.setByteCount(channelDataTrafficMap.get(channel.getChannelId()).getByteCount()); + } else { + channel.setByteCount(0); + } + } + + basePage.setRecords(channels); + resultObj.setData(basePage); + } + return resultObj; + } catch (Exception e) { + log.error("分页查询通道失败", e); + throw new RuntimeException("服务端连接失败"); + } + } + + /** + * 删除通道 + */ + @Override + public String deleteChannelById(String serverId, String channelId) { + ServerInfo server = serverMapper.selectById(serverId); + checkServerConnected(server); + + Map params = Collections.singletonMap("channelId", channelId); + return getFromServer(server, "/universaltestsoftware_server_api/outer/client/channelDeleteById", params); + } + + /** + * 获取通信通道路径 + */ + @Override + public String getChannelPath(String serverId, String boardId) { + ServerInfo server = serverMapper.selectById(serverId); + if (server == null || server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + + Map params = Collections.singletonMap("boardId", boardId); + return getFromServer(server, "/universaltestsoftware_server_api/common/getCommunicationChannelPath", params); + } + + @Override + public String refressBoardInformation(String serverId) { + ServerInfo server = serverMapper.selectById(serverId); + if (server == null || server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + return getFromServer(server, "/universaltestsoftware_server_api/common/refressBoardInformation", new HashMap<>()); + } + + @Override + public String getChannelTypeAll(String serverId, String boardTypeId) { + ServerInfo server = serverMapper.selectById(serverId); + if (server == null || server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + Map params = new HashMap<>(); + params.put("boardTypeId", boardTypeId); + return getFromServer(server, "/universaltestsoftware_server_api/outer/client/getChannelTypeAll", params); + } + + @Override + public String getBoardTemperature(BoardTemperatureDto dto) { + ServerInfo server = serverMapper.selectById(dto.getServerId()); + if (server == null || server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success()); + } + try { + BoardTemperatureServerDto serverDto = new BoardTemperatureServerDto(); + serverDto.setServerId(dto.getServerId()); + serverDto.setBoardId(dto.getBoardId()); + serverDto.setBoardIndex(Integer.parseInt(dto.getBoardIndex())); + return postToServer(server, "/universaltestsoftware_server_api/outer/client/getBoardTemperature", serverDto); + } catch (Exception e) { + return JSON.toJSONString(Result.success()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/BootLogServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/BootLogServiceImpl.java new file mode 100644 index 0000000..79132e4 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/BootLogServiceImpl.java @@ -0,0 +1,80 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.dto.BootLogDeleteDto; +import com.cbsd.universaltestsoftware_client.dto.BootLogPageDto; +import com.cbsd.universaltestsoftware_client.entity.BootLog; +import com.cbsd.universaltestsoftware_client.mapper.BootLogMapper; +import com.cbsd.universaltestsoftware_client.service.IBootLogService; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + *

+ * 服务实现类 + *

+ * + * @author wt + * @since 2025-09-03 + */ +@Service +public class BootLogServiceImpl extends ServiceImpl implements IBootLogService { + + + @Override + public Result> bootLogPage(BootLogPageDto bootLogPageDto) { + + if (bootLogPageDto.getLogType() == null || (bootLogPageDto.getLogType() != 1 && bootLogPageDto.getLogType() != 2)){ + bootLogPageDto.setLogType(1); + } + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.orderByDesc(BootLog::getTime) + .like(bootLogPageDto.getLogType() == 1, BootLog::getLogClass, "com.cbsd") + .notLike(bootLogPageDto.getLogType() == 2, BootLog::getLogClass, "com.cbsd") + .like(StringUtils.isNotEmpty(bootLogPageDto.getKeyword()), BootLog::getMessage, bootLogPageDto.getKeyword()) + .eq(StringUtils.isNotEmpty(bootLogPageDto.getLevel()), BootLog::getLevel, bootLogPageDto.getLevel()) + .between(StringUtils.isNotEmpty(bootLogPageDto.getEndTime()) && StringUtils.isNotEmpty(bootLogPageDto.getStartTime()), BootLog::getTime, bootLogPageDto.getStartTime(), bootLogPageDto.getEndTime()); + return Result.success(baseMapper.selectPage(bootLogPageDto, queryWrapper)); + } + + @Override + @Transactional + public Result bootLogDelete(BootLogDeleteDto bootLogDeleteDto) { + // 1. 统一校验:时间范围为空 且 无ID/批量ID/日志级别筛选时,直接返回参数错误 + boolean hasNoIdFilter = bootLogDeleteDto.getId() == null + && (bootLogDeleteDto.getListId() == null || bootLogDeleteDto.getListId().isEmpty()); + boolean hasNoLevelFilter = StringUtils.isEmpty(bootLogDeleteDto.getLevel()); + boolean hasNoTimeRange = StringUtils.isEmpty(bootLogDeleteDto.getStartTime()) + || StringUtils.isEmpty(bootLogDeleteDto.getEndTime()); + + // 核心校验逻辑:无任何筛选条件(ID/批量ID/级别 都没有,且时间范围也没有)→ 不允许删除 + if (hasNoIdFilter && hasNoLevelFilter && hasNoTimeRange) { + return Result.error("请选择要删除的日志(需指定ID/批量ID/日志级别或时间范围)"); + } + + // 2. 构建查询条件:使用LambdaQueryWrapper链式调用,条件判断更清晰,增加空集合安全校验 + LambdaQueryWrapper deleteQueryWrapper = new LambdaQueryWrapper() + // 单ID筛选:ID不为null时生效 + .eq(bootLogDeleteDto.getId() != null, BootLog::getId, bootLogDeleteDto.getId()) + // 批量ID筛选:集合不为null且非空时生效(避免空集合导致SQL语法错误) + .in(bootLogDeleteDto.getListId() != null && !bootLogDeleteDto.getListId().isEmpty(), + BootLog::getId, bootLogDeleteDto.getListId()) + // 日志级别筛选:级别不为空时生效 + .eq(StringUtils.isNotEmpty(bootLogDeleteDto.getLevel()), + BootLog::getLevel, bootLogDeleteDto.getLevel()) + // 时间范围筛选:开始/结束时间都不为空时生效 + .between(!hasNoTimeRange, BootLog::getTime, + bootLogDeleteDto.getStartTime(), bootLogDeleteDto.getEndTime()); + + // 3. 执行删除并返回结果:直接用变量接收删除行数,结果判断更简洁 + int deletedCount = baseMapper.delete(deleteQueryWrapper); + return deletedCount > 0 + ? Result.success("共删除:" + deletedCount + "条数据") + : Result.error("删除失败(无匹配日志或已被删除)"); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataHitServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataHitServiceImpl.java new file mode 100644 index 0000000..3338ae1 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataHitServiceImpl.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.entity.ChannelDataHit; +import com.cbsd.universaltestsoftware_client.service.ChannelDataHitService; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataHitMapper; +import org.springframework.stereotype.Service; + +/** +* @author SanJie +* @description 针对表【channel_data_hit(通道协议命中次数)】的数据库操作Service实现 +* @createDate 2026-01-23 16:24:09 +*/ +@Service +public class ChannelDataHitServiceImpl extends ServiceImpl + implements ChannelDataHitService{ + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataServiceImpl.java new file mode 100644 index 0000000..f2a8651 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataServiceImpl.java @@ -0,0 +1,491 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataHistoryDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelDataStatisticsDto; +import com.cbsd.universaltestsoftware_client.dto.PageChannelDataDto; +import com.cbsd.universaltestsoftware_client.dto.ServerPageChannelDataDto; +import com.cbsd.universaltestsoftware_client.entity.*; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.*; +import com.cbsd.universaltestsoftware_client.nettyclient.connection.ConnectionManager; +import com.cbsd.universaltestsoftware_client.parser.model.FiledParseData; +import com.cbsd.universaltestsoftware_client.service.ChannelDataService; +import com.cbsd.universaltestsoftware_client.service.SystemConfigService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.vo.ChannelDataStatisticsVo; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Service +@Log4j2 +public class ChannelDataServiceImpl extends ServiceImpl implements ChannelDataService { + + @Resource + private ParserMapper parserMapper; + + @Resource + private ParserProtocolMapper parserProtocolMapper; + + @Resource + private InstructMapper instructMapper; + + @Resource + private DataBoardChannelMapper dataBoardChannelMapper; + + @Resource + private ServerMapper serverMapper; + + @Resource + private ConnectionManager connectionManager; + + @Resource + private ChannelDataTrafficMapper channelDataTrafficMapper; + + @Resource + private ChannelDataHitMapper channelDataHitMapper; + + @Resource + private SystemConfigService systemConfigService; + + @Override + @Async + public void start(PageChannelDataDto pageChannelDataDto) { + + HashMap serverByDataBoardId = getServerByDataBoardId(pageChannelDataDto.getDataBoardId()); + List serverId = (List) serverByDataBoardId.get("serverInfos"); + + if (CollectionUtils.isNotEmpty(serverId)) { + //远程服务代码 + + for (ServerInfo serverInfo : serverId) { + try { + ServerInfo server = serverMapper.selectById(serverInfo.getServerId()); + if (server == null) continue; + ServerPageChannelDataDto serverPageChannelDataDto = new ServerPageChannelDataDto(); + List channelIds = (List) serverByDataBoardId.get("channelIds"); + serverPageChannelDataDto.setChannelIds(channelIds); + serverPageChannelDataDto.setDataBoardId(pageChannelDataDto.getDataBoardId()); + serverPageChannelDataDto.setPageSize(pageChannelDataDto.getPageSize()); + if (StringUtils.isNotEmpty(pageChannelDataDto.getStartTime())) { + serverPageChannelDataDto.setStartTime(DateUtils.stringToTimestamp(pageChannelDataDto.getStartTime())); + } + if (StringUtils.isNotEmpty(pageChannelDataDto.getEndTime())) { + serverPageChannelDataDto.setEndTime(DateUtils.stringToTimestamp(pageChannelDataDto.getEndTime())); + } + + String url = String.format("http://%s:%s/universaltestsoftware_server_api/outer/client/start", + server.getIpAddress(), server.getHttpPort()); + String result = HttpRequest.post(url) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(serverPageChannelDataDto)) + .execute() + .body(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + + } + + @Override + @Async + public void stop(String dataBoardId) { + //远程调用 + HashMap serverByDataBoardId = getServerByDataBoardId(dataBoardId); + List serverId = (List) serverByDataBoardId.get("serverInfos"); + if (CollectionUtils.isNotEmpty(serverId)) { + try { + HashMap hashMap = new HashMap<>(); + hashMap.put("dataBoardId", dataBoardId); + for (ServerInfo serverInfo : serverId) { + ServerInfo server = serverMapper.selectById(serverInfo.getServerId()); + if (server == null) continue; + String url = String.format("http://%s:%s/universaltestsoftware_server_api/outer/client/stop", + server.getIpAddress(), server.getHttpPort()); + String s = HttpUtil.get(url, hashMap); + + } + connectionManager.removeOriginalData(dataBoardId); + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void updateData(PageChannelDataDto pageChannelDataDto) { + // 更新分页参数 + HashMap serverByDataBoardId = getServerByDataBoardId(pageChannelDataDto.getDataBoardId()); + List serverId = (List) serverByDataBoardId.get("serverInfos"); + + if (CollectionUtils.isNotEmpty(serverId)) { + //远程服务代码 + try { + for (ServerInfo serverInfo : serverId) { + ServerInfo server = serverMapper.selectById(serverInfo.getServerId()); + if (server == null) continue; + ServerPageChannelDataDto serverPageChannelDataDto = new ServerPageChannelDataDto(); + List channelIds = (List) serverByDataBoardId.get("channelIds"); + serverPageChannelDataDto.setChannelIds(channelIds); + serverPageChannelDataDto.setPageSize(pageChannelDataDto.getPageSize()); + serverPageChannelDataDto.setDataBoardId(pageChannelDataDto.getDataBoardId()); + if (StringUtils.isNotEmpty(pageChannelDataDto.getStartTime())) { + serverPageChannelDataDto.setStartTime(DateUtils.stringToTimestamp(pageChannelDataDto.getStartTime())); + } + if (StringUtils.isNotEmpty(pageChannelDataDto.getEndTime())) { + serverPageChannelDataDto.setEndTime(DateUtils.stringToTimestamp(pageChannelDataDto.getEndTime())); + } + String url = String.format("http://%s:%s/universaltestsoftware_server_api/outer/client/updateData", + server.getIpAddress(), server.getHttpPort()); + String result = HttpRequest.post(url) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(serverPageChannelDataDto)) + .execute() + .body(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + @Override + public String channelDataQuery(ChannelDataHistoryDto channelDataHistoryDto) { + ServerInfo server = serverMapper.selectById(channelDataHistoryDto.getServerId()); + if (server == null) { + return JSON.toJSONString(Result.success()); + } + if (server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + return postToServer(server, "/universaltestsoftware_server_api/outer/client/channelDataQuery", channelDataHistoryDto); + + } + + @Override + public String channelDataDelete(ChannelDataHistoryDto channelDataHistoryDto) { + ServerInfo server = serverMapper.selectById(channelDataHistoryDto.getServerId()); + if (server == null) { + return JSON.toJSONString(Result.success()); + } + if (server.getConnectStatus() == 0) { + return JSON.toJSONString(Result.success(new ArrayList<>())); + } + return postToServer(server, "/universaltestsoftware_server_api/outer/client/channelDataDelete", channelDataHistoryDto); + } + + /** + * 构造服务端请求基础URL + */ + private String buildUrl(ServerInfo server, String path) { + return String.format("http://%s:%s%s", server.getIpAddress(), server.getHttpPort(), path); + } + + /** + * 获取指定channelId的最新数据 + * + * @param channelId 通道id + */ + public Map getChannelLatestData(String channelId) { + Map map = new HashMap<>(); + if (StringUtils.isNotBlank(channelId)) { + List parserList = parserMapper.selectList(new LambdaQueryWrapper().eq(Parser::getChannelId, channelId)); + if (parserList != null && !parserList.isEmpty()) { + List parserIdList = parserList.stream().map(Parser::getParserId).collect(Collectors.toList()); + List protocolList = parserProtocolMapper.selectList(new LambdaQueryWrapper().in(ParserProtocol::getParserId, parserIdList)); + if (protocolList != null && !protocolList.isEmpty()) { + List protocolIdList = new ArrayList<>(); + for (ParserProtocol parserProtocol : protocolList) { + if (!protocolIdList.contains(parserProtocol.getProtocolId())) { + protocolIdList.add(parserProtocol.getProtocolId()); + } + } + for (String protocolId : protocolIdList) { + ChannelData data = baseMapper.selectOne(new LambdaQueryWrapper() + .eq(ChannelData::getChannelId, channelId) + .eq(ChannelData::getProtocolId, protocolId) + .orderByDesc(ChannelData::getTime) + .last("LIMIT 1")); + if (data != null && StringUtils.isNotBlank(data.getData())) { + try { + List filedParseDataList = JSON.parseArray(data.getData(), FiledParseData.class); + for (FiledParseData filedParseData : filedParseDataList) { + map.computeIfAbsent(filedParseData.getLabel(), k -> filedParseData.getPhyValue()); + } + } catch (Exception e) { + + } + } + } + } + } + } + return map; + } + + /** + * 通用POST请求封装 + */ + private String postToServer(ServerInfo server, String path, Object body) { + try { + String url = buildUrl(server, path); + String result = HttpRequest.post(url) + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(body)) + .execute() + .body(); + if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isBlank(result)) { + throw new RuntimeException("服务端返回为空"); + } + return result; + } catch (Exception e) { + log.error("请求服务端失败, path={}, error={}", path, e.getMessage()); + throw new RuntimeException("服务端连接失败"); + } + } + + + //通过看板id获取服务端 + public HashMap getServerByDataBoardId(String dataBoardId) { + HashMap map = new HashMap<>(); + //查询这个看板有哪些通道id + LambdaQueryWrapper dataBoardChannelLambdaQueryWrapper = new LambdaQueryWrapper<>(); + dataBoardChannelLambdaQueryWrapper.eq(DataBoardChannel::getDataBoardId, dataBoardId) + .groupBy(DataBoardChannel::getChannelId) + ; + List dataBoardChannelList = dataBoardChannelMapper.selectList(dataBoardChannelLambdaQueryWrapper); + List channelIds = dataBoardChannelList.stream().map(DataBoardChannel::getChannelId).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(channelIds)) { + //查询这个通道有那些服务端 + LambdaQueryWrapper instructLambdaQueryWrapper = new LambdaQueryWrapper<>(); + instructLambdaQueryWrapper.in(Instruct::getChannelId, channelIds) + .groupBy(Instruct::getServerId) + ; + List instructList = instructMapper.selectList(instructLambdaQueryWrapper); + + List serverIds = instructList.stream().map(Instruct::getServerId).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(serverIds)) { + return map; + } + List serverInfos = serverMapper.selectList(new LambdaQueryWrapper().in(ServerInfo::getServerId, serverIds)); + map.put("serverInfos", serverInfos); + map.put("channelIds", channelIds); + + } + return map; + } + + @Override + public Result> getChannelDataStatistics(ChannelDataStatisticsDto dto) { + if (StringUtils.isBlank(dto.getDataBoardId())) { + return Result.error("数据看板id不能为空"); + } + + List channelIdList = dataBoardChannelMapper.getChannelList(dto.getDataBoardId()); + if (channelIdList == null || channelIdList.isEmpty()) { + return Result.success(new ArrayList<>()); + } + List resultList = new ArrayList<>(); + channelIdList.forEach(channelId -> { + ChannelDataStatisticsVo vo = new ChannelDataStatisticsVo(); + vo.setChannelId(channelId); + resultList.add(vo); + }); + + //通道数据流量 + List channelDataTrafficList = channelDataTrafficMapper.selectList(new LambdaQueryWrapper().in(ChannelDataTraffic::getChannelId, channelIdList)); + Map channelDataTrafficMap = null; + if (channelDataTrafficList != null && !channelDataTrafficList.isEmpty()) { + channelDataTrafficMap = channelDataTrafficList.stream() + .collect(Collectors.toMap(ChannelDataTraffic::getChannelId, channelDataTraffic -> channelDataTraffic)); + } + //查询通道的解析器 + List parserList = parserMapper.selectList(new LambdaQueryWrapper().in(Parser::getChannelId, channelIdList)); + //通道协议map + Map> channelProtocolMap = new HashMap<>(); + if (parserList != null && !parserList.isEmpty()) { + //通道解析器map + Map> channelParserMap = parserList.stream().collect(Collectors.groupingBy(Parser::getChannelId)); + List parserIdList = parserList.stream().map(Parser::getParserId).collect(Collectors.toList()); + List allParserProtocolList = parserProtocolMapper.selectList(new LambdaQueryWrapper() + .in(ParserProtocol::getParserId, parserIdList) + .orderByAsc(ParserProtocol::getCreateTime)); + //解析器协议map + Map> parserProtocolMap = allParserProtocolList.stream().collect(Collectors.groupingBy(ParserProtocol::getParserId)); + + for (String channelId : channelIdList) { + List channelParserList = channelParserMap.get(channelId); + if (channelParserList != null && !channelParserList.isEmpty()) { + List protocolIdList = new ArrayList<>(); + for (Parser parser : channelParserList) { + List parserProtocolList = parserProtocolMap.get(parser.getParserId()); + if (parserProtocolList != null && !parserProtocolList.isEmpty()) { + for (ParserProtocol parserProtocol : parserProtocolList) { + protocolIdList.add(parserProtocol.getProtocolId()); + } + } + } + channelProtocolMap.put(channelId, protocolIdList); + } + } + + } + + for (ChannelDataStatisticsVo vo : resultList) { + //通道数据流量 + if (channelDataTrafficMap != null && channelDataTrafficMap.get(vo.getChannelId()) != null) { + vo.setDataByteCount(channelDataTrafficMap.get(vo.getChannelId()).getByteCount()); + } else { + vo.setDataByteCount(0); + } + List protocolStatisticsList = new ArrayList<>(); + + List channelProtocolIdList = channelProtocolMap.get(vo.getChannelId()); + if (channelProtocolIdList != null && !channelProtocolIdList.isEmpty()) { + for (String protocolId : channelProtocolIdList) { + ChannelDataStatisticsVo.ProtocolStatisticsVo protocolStatisticsVo = new ChannelDataStatisticsVo.ProtocolStatisticsVo(); + protocolStatisticsVo.setProtocolId(protocolId); + ChannelDataHit dataHit = channelDataHitMapper.selectOne(new LambdaQueryWrapper() + .eq(ChannelDataHit::getChannelId, vo.getChannelId()) + .eq(ChannelDataHit::getProtocolId, protocolId) + .last("LIMIT 1")); + protocolStatisticsVo.setParseCount(dataHit != null ? dataHit.getHitCount() : 0); + protocolStatisticsList.add(protocolStatisticsVo); + } + } + vo.setProtocolStatisticsList(protocolStatisticsList); + } + return Result.success(resultList); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Result clearChannelDataStatistics(ChannelDataStatisticsDto dto) { + if (StringUtils.isBlank(dto.getChannelId())) { + return Result.error("通道id不能为空"); + } + try { + channelDataTrafficMapper.update(null, new LambdaUpdateWrapper() + .set(ChannelDataTraffic::getByteCount, 0) + .eq(ChannelDataTraffic::getChannelId, dto.getChannelId())); + //查询通道的解析器 + List parserList = parserMapper.selectList(new LambdaQueryWrapper().eq(Parser::getChannelId, dto.getChannelId())); + if (parserList != null && !parserList.isEmpty()) { + List parserIdList = parserList.stream().map(Parser::getParserId).collect(Collectors.toList()); + List allParserProtocolList = parserProtocolMapper.selectList(new LambdaQueryWrapper() + .in(ParserProtocol::getParserId, parserIdList)); + if (allParserProtocolList != null && !allParserProtocolList.isEmpty()) { + List protocolIdList = allParserProtocolList.stream().map(ParserProtocol::getProtocolId).collect(Collectors.toList()); + channelDataHitMapper.update(null, new LambdaUpdateWrapper() + .set(ChannelDataHit::getHitCount, 0) + .eq(ChannelDataHit::getChannelId, dto.getChannelId()) + .in(ChannelDataHit::getProtocolId, protocolIdList)); + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new BaseException("清除失败"); + } + + return Result.success(1); + } + + @Override + public void clearChannelData() { + // 异步通知服务端清理数据(不阻塞主线程) + CompletableFuture.runAsync(() -> { + System.out.println("-----------开始清理数据----------"); + try { + String dataClearLimitStr = systemConfigService.getSystemConfig(2); + if (StringUtils.isNotBlank(dataClearLimitStr)) { + int dataClearLimit = Integer.parseInt(dataClearLimitStr); + if (dataClearLimit > 0) { + List serverInfos = serverMapper.selectList( + new LambdaQueryWrapper() + .eq(ServerInfo::getConnectStatus, 1) + .eq(ServerInfo::getIsDelete, "0") + ); + for (ServerInfo serverInfo : serverInfos) { + try { + // 只有在线的服务端才通知 + if (serverInfo.getConnectStatus() == 1) { + System.out.println("【异步】通知服务端清理数据: " + serverInfo.getIpAddress()); + HashMap hashMap = new HashMap<>(); + hashMap.put("clearCount", dataClearLimit); + String url = String.format( + "http://%s:%s/universaltestsoftware_server_api/outer/client/channelDataClear", + serverInfo.getIpAddress(), + serverInfo.getHttpPort() + ); + String s = HttpUtil.get(url, hashMap); // 注意:确保 HttpUtil.get 是线程安全的 + } + } catch (Exception e) { + e.printStackTrace(); + } + } + checkDataLimitToClear(dataClearLimit); + log.info("【channel_data】数据清理完成"); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + private void checkDataLimitToClear(int dataClearLimit) { + long count = count(); + long deleteCount = count - dataClearLimit; + System.out.println("【异步】数据超出限制,需要清理数据数量: " + deleteCount); + if (deleteCount > 0) { + // 1. 分批删除,每批1000条 + long batchSize = 1000; + long totalDeleted = 0; + //清除数据 + while (totalDeleted < deleteCount) { + try { + long currentBatchSize = Math.min(batchSize, deleteCount - totalDeleted); + + // 方法1:直接删除(小批量) + baseMapper.delete(new LambdaQueryWrapper() + .orderByAsc(ChannelData::getTime) + .last("LIMIT " + currentBatchSize)); + + totalDeleted += currentBatchSize; + + // 可选:每次批次后短暂休眠,减少对数据库的压力 + if (totalDeleted < deleteCount) { + Thread.sleep(10); // 10毫秒 + } + } catch (Exception ignored) { + + } + } + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataTrafficServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataTrafficServiceImpl.java new file mode 100644 index 0000000..a536e1b --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ChannelDataTrafficServiceImpl.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.entity.ChannelDataTraffic; +import com.cbsd.universaltestsoftware_client.service.ChannelDataTrafficService; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataTrafficMapper; +import org.springframework.stereotype.Service; + +/** +* @author SanJie +* @description 针对表【channel_data_traffic(通道流量统计)】的数据库操作Service实现 +* @createDate 2026-01-23 10:07:19 +*/ +@Service +public class ChannelDataTrafficServiceImpl extends ServiceImpl + implements ChannelDataTrafficService{ + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DataBoardChannelServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DataBoardChannelServiceImpl.java new file mode 100644 index 0000000..6f50ded --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DataBoardChannelServiceImpl.java @@ -0,0 +1,20 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.cbsd.universaltestsoftware_client.entity.DataBoardChannel; +import com.cbsd.universaltestsoftware_client.mapper.DataBoardChannelMapper; +import com.cbsd.universaltestsoftware_client.service.DataBoardChannelService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author admin + * @since 2025-09-10 + */ +@Service +public class DataBoardChannelServiceImpl extends ServiceImpl implements DataBoardChannelService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DataBoardServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DataBoardServiceImpl.java new file mode 100644 index 0000000..75b012e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DataBoardServiceImpl.java @@ -0,0 +1,759 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import cn.hutool.http.HttpRequest; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.dto.*; +import com.cbsd.universaltestsoftware_client.entity.*; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.*; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.parser.model.FiledParseData; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import com.cbsd.universaltestsoftware_client.service.DataBoardChannelService; +import com.cbsd.universaltestsoftware_client.service.DataBoardService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.XmlToObjectConverter; +import com.cbsd.universaltestsoftware_client.vo.ChannelDataHistoryVo; +import com.opencsv.CSVWriter; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +/** + *

+ * 数据看板 服务实现类 + *

+ * + * @author admin + * @since 2025-09-10 + */ +@Service +public class DataBoardServiceImpl extends ServiceImpl implements DataBoardService { + + @Resource + private ParserMapper parserMapper; + + @Resource + private ParserProtocolMapper parserProtocolMapper; + + @Resource + private ProtocolMapper protocolMapper; + + @Resource + private DataBoardChannelMapper dataBoardChannelMapper; + + + @Resource + private ChannelDataMapper channelDataMapper; + + + @Resource + private DataBoardChannelService dataBoardChannelService; + + @Resource + private ServerMapper serverMapper; + + + @Resource + private DownloadMapper downloadMapper; + + @Value("${cbsd.downloadUrl}") + private String downloadDir; + + + List colorList = Arrays.asList( + "#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de", "#3ba272", "#fc8452", "#9a60b4", "#8798cc", + "#75ccc3", "#faa458", "#e87bec", "#55dc98", "#5db548", "#c39d70", "#b977f1", "#f7a5ef", "#c0c447", + "#f59472", "#f9868d", "#9cd8de", "#26c8cb", "#d5b9ff", "#39aae5", "#eda18a", "#24a5a7", "#9db9cd", + "#fe73d8", "#74d9ff", "#fbbead", "#eee965", "#c7dfe8" + ); + + /** + * 构造服务端请求基础URL + */ + private String buildUrl(ServerInfo server, String path) { + return String.format("http://%s:%s%s", server.getIpAddress(), server.getHttpPort(), path); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveOrUpdateDataBoard(InsertDataBoardDto insertDataBoardDto) throws Exception { + DataBoard dataBoard = new DataBoard(); + dataBoard.setDataBoardName(insertDataBoardDto.getDataBoardName()); + dataBoard.setDataBoardType(insertDataBoardDto.getDataBoardType()); + dataBoard.setCreateTime(DateUtils.dateToString()); + dataBoard.setDataType(insertDataBoardDto.getDataType()); + if (StringUtils.isNotBlank(insertDataBoardDto.getDataBoardId())) { + dataBoard.setDataBoardId(insertDataBoardDto.getDataBoardId()); + baseMapper.updateById(dataBoard); + //清除所有通道 + //查询所有通道 + if (CollectionUtils.isEmpty(insertDataBoardDto.getChannelList())) { + List dataBoardChannels = dataBoardChannelMapper.selectList(new LambdaUpdateWrapper().eq(DataBoardChannel::getDataBoardId, insertDataBoardDto.getDataBoardId()) + .groupBy(DataBoardChannel::getChannelId) + ); + ArrayList channelDtos = new ArrayList<>(); + for (DataBoardChannel dataBoardChannel : dataBoardChannels) { + ChannelDto channelDto = new ChannelDto(); + channelDto.setChannelId(dataBoardChannel.getChannelId()); + channelDto.setChannelName(dataBoardChannel.getChannelName()); + channelDtos.add(channelDto); + + } + insertDataBoardDto.setChannelList(channelDtos); + } + + dataBoardChannelMapper.update(null, new UpdateWrapper().set("protocol_id", null) + .set("protocol_name", null) + .eq("data_board_id", insertDataBoardDto.getDataBoardId()) + ); + + + } else { + + if (StringUtils.isBlank(insertDataBoardDto.getDataBoardName()) || insertDataBoardDto.getDataBoardType() == null) { + throw new Exception("数据看板名字和类型不能为空"); + } + if (CollectionUtils.isEmpty(insertDataBoardDto.getChannelList())) { + throw new Exception("数据看板通道不能为空"); + } + baseMapper.insert(dataBoard); + } + for (ChannelDto channelDto : insertDataBoardDto.getChannelList()) { + //参数列表 + List parsers = parserMapper.selectList(new LambdaUpdateWrapper().eq(Parser::getChannelId, channelDto.getChannelId())); + //查询这个解析器有那些协议 + if (CollectionUtils.isNotEmpty(parsers)) { + List parserIds = parsers.stream().map(Parser::getParserId).distinct().collect(Collectors.toList()); + List parserProtocols = parserProtocolMapper.selectList(new LambdaUpdateWrapper().in(ParserProtocol::getParserId, parserIds)); + if (CollectionUtils.isNotEmpty(parserProtocols)) { + List protocolIds = parserProtocols.stream().map(ParserProtocol::getProtocolId).distinct().collect(Collectors.toList()); + List protocols = protocolMapper.selectList(new LambdaUpdateWrapper().in(Protocol::getProtocolId, protocolIds)); + //解析协议内容的参数 + // List protocolContents = protocols.stream().map(Protocol::getProtocolContent).distinct().collect(Collectors.toList()); + for (Protocol protocol : protocols) { + HashMap parameter = new HashMap<>(); + List dataBoardChannelList = new ArrayList<>(); + if (StringUtils.isEmpty(protocol.getProtocolContent())) { + continue; + } + ProtocolXml protocolXml = XmlToObjectConverter.convertXmlToObject(protocol.getProtocolContent(), ProtocolXml.class); + + List filedList = protocolXml.getFileds(); + //根据sn排序升序 + filedList.sort(Comparator.comparingInt(Filed::getSnInt)); + for (Filed filed : filedList) { + //判断字段类型是否为解析字段 +// if (filed.getType().equalsIgnoreCase(ProtocolEnum.VAR.getValue())) { +// String readName = filed.getReadName(); +// if (StringUtils.isNotBlank(readName)) { +// parameter.put(readName, filed.getSnInt()); +// } +// +// } + + String readName = filed.getReadName(); + if (StringUtils.isNotBlank(readName)) { + parameter.put(readName, filed.getSnInt()); + } + } + //循环Map参数 并帮颜色循坏赋值,如果颜色不够则又从头循环 + int j = 0; + for (Map.Entry entry : parameter.entrySet()) { + if (j >= colorList.size()) { + j = 0; + } + String s = entry.getKey(); + int i = entry.getValue(); + DataBoardChannel dataBoardChannel = new DataBoardChannel(); + //查看是是否存在 + DataBoardChannel dataBoardChannel1 = dataBoardChannelMapper. + selectOne(new LambdaUpdateWrapper().eq(DataBoardChannel::getDataBoardId, dataBoard.getDataBoardId()) + .eq(DataBoardChannel::getChannelId, channelDto.getChannelId()) + .eq(DataBoardChannel::getProtocolId, protocol.getProtocolId()) +// .eq(DataBoardChannel::getParameter, s) + ); + if (dataBoardChannel1 != null) { + dataBoardChannel = dataBoardChannel1; + } + dataBoardChannel.setDataBoardId(dataBoard.getDataBoardId()); + dataBoardChannel.setChannelId(channelDto.getChannelId()); + dataBoardChannel.setChannelName(channelDto.getChannelName()); + dataBoardChannel.setParameter(s); + dataBoardChannel.setSn(i); + dataBoardChannel.setProtocolId(protocol.getProtocolId()); + dataBoardChannel.setProtocolName(protocol.getProtocolName()); + dataBoardChannel.setLegendColor(colorList.get(j)); + + dataBoardChannelList.add(dataBoardChannel); + j++; + } + + if (!dataBoardChannelList.isEmpty()) { + int batchSize = 1000; + int total = dataBoardChannelList.size(); + if (total > batchSize) { + int insertTimes = total / batchSize; + int lastSize = total % batchSize; + if (lastSize > 0) { + insertTimes += 1; + } + for (int i = 0; i < insertTimes; i++) { + List insertList; + if (lastSize > 0 && i == insertTimes - 1) { + insertList = dataBoardChannelList.subList(i * batchSize, i * batchSize + lastSize); + } else { + insertList = dataBoardChannelList.subList(i * batchSize, i * batchSize + batchSize); + } + dataBoardChannelService.saveOrUpdateBatch(insertList); + } + } else { + dataBoardChannelService.saveOrUpdateBatch(dataBoardChannelList); + } + } else { + throw new BaseException("通道无有效协议或协议中无参数字段"); + } + } + } + } + + } + + + } + + @Override + public void removeDataBoardById(String dataBoardId) { + baseMapper.deleteById(dataBoardId); + dataBoardChannelMapper.delete(new LambdaUpdateWrapper().eq(DataBoardChannel::getDataBoardId, dataBoardId)); + } + + @Override + public List listDataBoard(Integer dataType) { + List dataBoardList = baseMapper.selectList(new LambdaUpdateWrapper().eq(DataBoard::getDataType, dataType) + .orderByAsc(DataBoard::getCreateTime) + ); + for (DataBoard dataBoard : dataBoardList) { + List dataBoardChannelList = dataBoardChannelMapper.selectList(new LambdaUpdateWrapper() + .eq(DataBoardChannel::getDataBoardId, dataBoard.getDataBoardId()) + .groupBy(DataBoardChannel::getChannelId) + ); + + for (DataBoardChannel dataBoardChannel : dataBoardChannelList) { + //查询这个通道里有多少协议 + List protocolIds = dataBoardChannelMapper.selectList(new LambdaUpdateWrapper() + .eq(DataBoardChannel::getChannelId, dataBoardChannel.getChannelId()) + .eq(DataBoardChannel::getDataBoardId, dataBoard.getDataBoardId()) + .groupBy(DataBoardChannel::getProtocolId) + ); + ArrayList protocolNameListDtos = new ArrayList<>(); + + for (DataBoardChannel protocolId : protocolIds) { + if (StringUtils.isEmpty(protocolId.getProtocolId())) { + continue; + } + ProtocolNameListDto protocolNameListDto = new ProtocolNameListDto(); + List parameterList = dataBoardChannelMapper.selectList(new LambdaUpdateWrapper() + .eq(DataBoardChannel::getChannelId, dataBoardChannel.getChannelId()) + .eq(DataBoardChannel::getDataBoardId, dataBoard.getDataBoardId()) + .eq(DataBoardChannel::getProtocolId, protocolId.getProtocolId()) + .orderByAsc(DataBoardChannel::getSn) + ); + BeanUtils.copyProperties(dataBoardChannel, protocolNameListDto); + protocolNameListDto.setProtocolId(protocolId.getProtocolId()); + protocolNameListDto.setProtocolName(protocolId.getProtocolName()); + protocolNameListDto.setParameterList(parameterList); + protocolNameListDtos.add(protocolNameListDto); + } + dataBoardChannel.setProtocolNameList(protocolNameListDtos); + } + + dataBoard.setDataBoardChannelList(dataBoardChannelList); + } + return dataBoardList; + } + + @Override + public void updateDataBoardById(DataBoardChannel dataBoardChannel) { + dataBoardChannelMapper.updateById(dataBoardChannel); + } + + @Override + public Result exportDataBoard(ExportDataBoardDto dto, HttpServletResponse response) { + + // ====== 1. 基础参数校验 ====== + if (dto.getExportType() == null) { + return Result.error("请选择导出类型"); + } + DataBoard dataBoard = baseMapper.selectById(dto.getDataBoardId()); + if (dataBoard == null) { + return Result.error("看板不存在"); + } + String exportName = dataBoard.getDataBoardName(); + + // ====== 2. 初始化 Download 记录(不管有没有数据,先创建)====== + Download download = new Download(); + String downloadId = UUID.randomUUID().toString().replace("-", ""); + download.setDownloadId(downloadId); + download.setStartTime(dto.getStartTime()); + download.setEndTime(dto.getEndTime()); + download.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + download.setState(1); // 初始状态:-1 表示“待处理/校验中” + download.setType(dto.getExportType()); + download.setChannelName(""); // 先空着,后面有数据再填 + download.setProtocolName(""); + download.setDownloadUrl(""); + downloadMapper.insert(download); + + // ====== 3. 异步执行整个流程(包括校验 + 导出)====== + new Thread(() -> { + try { + String channelName = ""; + String protocolName = ""; + boolean hasData = false; + + if (dto.getExportType() == 1) { + List channels = dataBoardChannelMapper.selectList(new LambdaQueryWrapper() + .eq(DataBoardChannel::getDataBoardId, dto.getDataBoardId()) + .in(CollectionUtils.isNotEmpty(dto.getParam()), DataBoardChannel::getParameter, dto.getParam()) + .in(CollectionUtils.isNotEmpty(dto.getProtocolIds()), DataBoardChannel::getProtocolId, dto.getProtocolIds()) + ); + + if (CollectionUtils.isNotEmpty(channels)) { + List channelIds = channels.stream().map(DataBoardChannel::getChannelId).collect(Collectors.toList()); + List protocolIds = channels.stream().map(DataBoardChannel::getProtocolId).collect(Collectors.toList()); + + List dataList = channelDataMapper.selectList(new LambdaQueryWrapper() + .between(ChannelData::getTime, dto.getStartTime(), dto.getEndTime()) + .in(CollectionUtils.isNotEmpty(channelIds), ChannelData::getChannelId, channelIds) + .in(CollectionUtils.isNotEmpty(protocolIds), ChannelData::getProtocolId, protocolIds) + ); + + if (CollectionUtils.isNotEmpty(dataList)) { + // 检查是否有匹配字段的有效数据 + for (ChannelData cd : dataList) { + if (StringUtils.isNotBlank(cd.getData())) { + List list = JSON.parseArray(cd.getData(), FiledParseData.class); + for (FiledParseData fpd : list) { + if (CollectionUtils.isEmpty(dto.getParam()) || dto.getParam().contains(fpd.getReadName())) { + hasData = true; + break; + } + } + if (hasData) break; + } + } + + if (hasData) { + channelName = channels.stream().map(DataBoardChannel::getChannelName).distinct().collect(Collectors.joining(",")); + protocolName = channels.stream().map(DataBoardChannel::getProtocolName).distinct().collect(Collectors.joining(",")); + } + } + } + + } else if (dto.getExportType() == 2) { + List channels = dataBoardChannelMapper.selectList(new LambdaQueryWrapper() + .eq(DataBoardChannel::getDataBoardId, dto.getDataBoardId()) + .in(CollectionUtils.isNotEmpty(dto.getParam()), DataBoardChannel::getParameter, dto.getParam()) + .in(CollectionUtils.isNotEmpty(dto.getProtocolIds()), DataBoardChannel::getProtocolId, dto.getProtocolIds()) + ); + + if (CollectionUtils.isNotEmpty(channels)) { + List channelIds = new ArrayList<>(); + + for (DataBoardChannel channel : channels) { + if (!channelIds.contains(channel.getChannelId())) { + channelIds.add(channel.getChannelId()); + } + } + if (CollectionUtils.isNotEmpty(channelIds)) { + ChannelDataQueryAll query = new ChannelDataQueryAll(); + query.setChannelIds(channelIds); + query.setStartTime(dto.getStartTime()); + query.setEndTime(dto.getEndTime()); + + List servers = serverMapper.selectList(new LambdaQueryWrapper() + .eq(ServerInfo::getConnectStatus, 1) + .eq(ServerInfo::getIsDelete, "0") + ); + + for (ServerInfo server : servers) { + try { + String resp = postToServer(server, "/universaltestsoftware_server_api/outer/client/channelDataQueryAll", query); + if (StringUtils.isNotBlank(resp) && !resp.equals("[]")) { + hasData = true; + break; + } + } catch (Exception ignored) { + } + } + + if (hasData) { + channelName = channels.stream().map(DataBoardChannel::getChannelName).distinct().collect(Collectors.joining(",")); + protocolName = channels.stream().map(DataBoardChannel::getProtocolName).distinct().collect(Collectors.joining(",")); + } + } + } + } + + // ====== 4. 根据是否有数据,决定后续操作 ====== + if (hasData) { + // 更新通道/协议名 + Download updateNames = new Download(); + updateNames.setDownloadId(downloadId); + updateNames.setChannelName(channelName); + updateNames.setProtocolName(protocolName); + downloadMapper.updateById(updateNames); + + // 执行导出 + LocalDateTime dt = LocalDateTime.parse(download.getCreateTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String timeStr = dt.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String finalExportName = exportName + "_" + timeStr; + + doExportInBackground(finalExportName, downloadId, dto); + // doExportInBackground 内部会把 state 更新为 2 + } else { + // 无数据:直接标记为“无数据”状态(比如 state = -1) + Download noDataUpdate = new Download(); + noDataUpdate.setDownloadId(downloadId); + noDataUpdate.setState(-1); // -1 表示“无数据” + downloadMapper.updateById(noDataUpdate); + } + + } catch (Exception e) { + // 异常:标记为失败 + e.printStackTrace(); + Download failUpdate = new Download(); + failUpdate.setDownloadId(downloadId); + failUpdate.setState(-2); // -2 表示“系统错误” + downloadMapper.updateById(failUpdate); + } + }).start(); + + // ====== 5. 立即返回 downloadId 给前端 ====== + return Result.success(); + + + } + + @Override + public void clear(ExportDataBoardDto exportDataBoardDto) { + List dataBoardChannels = dataBoardChannelMapper.selectList(new LambdaQueryWrapper() + .eq(DataBoardChannel::getDataBoardId, exportDataBoardDto.getDataBoardId()) + .in(CollectionUtils.isNotEmpty(exportDataBoardDto.getParam()), DataBoardChannel::getParameter, exportDataBoardDto.getParam()) + .in(CollectionUtils.isNotEmpty(exportDataBoardDto.getProtocolIds()), DataBoardChannel::getProtocolId, exportDataBoardDto.getProtocolIds()) + + ); + if (CollectionUtils.isNotEmpty(dataBoardChannels)) { + List channelIds = new ArrayList<>(); + List protocolIds = new ArrayList<>(); + for (DataBoardChannel dataBoardChannel : dataBoardChannels) { + if (!channelIds.contains(dataBoardChannel.getChannelId())) { + channelIds.add(dataBoardChannel.getChannelId()); + } + if (!protocolIds.contains(dataBoardChannel.getProtocolId())) { + protocolIds.add(dataBoardChannel.getProtocolId()); + } + } + + channelDataMapper.delete(new LambdaQueryWrapper() + .in(CollectionUtils.isNotEmpty(channelIds), ChannelData::getChannelId, channelIds) + .in(CollectionUtils.isNotEmpty(protocolIds), ChannelData::getProtocolId, protocolIds) + .between(ChannelData::getTime, exportDataBoardDto.getStartTime(), exportDataBoardDto.getEndTime())); + + //异步远程调用清理服务端数据 + CompletableFuture.runAsync(() -> { + List servers = serverMapper.selectList(new LambdaQueryWrapper() + .eq(ServerInfo::getConnectStatus, 1) + .eq(ServerInfo::getIsDelete, "0") + ); + DeleteChannelDto deleteChannelDto = new DeleteChannelDto(); + deleteChannelDto.setChannelIds(channelIds); + deleteChannelDto.setStartTime(exportDataBoardDto.getStartTime()); + deleteChannelDto.setEndTime(exportDataBoardDto.getEndTime()); + for (ServerInfo server : servers) { + try { + String resp = postToServer(server, "/universaltestsoftware_server_api/outer/client/deleteByIds", deleteChannelDto); + System.out.println("清除服务端数据结果:" + resp); + } catch (Exception ignored) { + } + } + }); + } + + } + + @Override + public Result> exportHistory(DownloadDto downloadDto) { + LambdaUpdateWrapper dataBoardLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); + dataBoardLambdaUpdateWrapper.between(StringUtils.isNotBlank(downloadDto.getStartTime()) && StringUtils.isNotBlank(downloadDto.getEndTime()), Download::getCreateTime, + downloadDto.getStartTime(), downloadDto.getEndTime()) + .eq(downloadDto.getState() != null, Download::getState, downloadDto.getState()) + .eq(downloadDto.getType() != null, Download::getType, downloadDto.getType()) + .and(StringUtils.isNotBlank(downloadDto.getKeyword()), + e -> e + .like(Download::getChannelName, downloadDto.getKeyword()) + .or() + .like(Download::getProtocolName, downloadDto.getKeyword()) + ) + .orderByDesc(Download::getCreateTime) + ; + DownloadDto downloadDto1 = downloadMapper.selectPage(downloadDto, dataBoardLambdaUpdateWrapper); + + return Result.success(downloadDto1); + + } + + private String postToServer(ServerInfo server, String path, Object body) { + try { + String url = buildUrl(server, path); + String result = HttpRequest.post(url) + .timeout(30 * 1000) + .setConnectionTimeout(30 * 1000) + .setReadTimeout(60 * 1000) + .charset(StandardCharsets.UTF_8) // ⭐ 核心:统一 UTF-8 + .header("Content-Type", "application/json;charset=UTF-8") + .body(JSONUtil.toJsonStr(body)) + .execute() + .body(); + if (StringUtils.isBlank(result)) { + throw new RuntimeException("服务端返回为空"); + } + return result; + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("服务端连接失败"); + } + } + + /** + * 后台真实执行导出的方法(异步调用) + */ + private void doExportInBackground(String exportName, String downloadId, ExportDataBoardDto dto) { + File dir = new File(downloadDir); + if (!dir.exists()) { + dir.mkdirs(); + } + + String exportFileName = ""; + + boolean exportSuccess = false; + + //20260208 需求:导出原数据时,将原数据拼接保存为.dat文件 + if (dto.getExportType() == 2) { + //原数据 + exportFileName = exportName + ".dat"; + File file = new File(dir, exportFileName); + try { + handleExportSourceData(dto, file); + exportSuccess = true; + } catch (Exception e) { + + } + } else if (dto.getExportType() == 1) { + //解析数据 + exportFileName = exportName + ".csv"; + File file = new File(dir, exportFileName); + + try (FileOutputStream fos = new FileOutputStream(file); + OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { + // 写入 BOM,避免 Excel 打开中文乱码 + osw.write('\ufeff'); + try (CSVWriter writer = new CSVWriter(osw)) { + handleExportParseData(dto, writer); + } + exportSuccess = true; + } catch (Exception e) { + // 记录错误(实际项目建议用 log.error) + e.printStackTrace(); + // 注意:当前设计只在成功时更新状态;失败时 state 仍为 1(生成中) + } + } + + // 导出成功,更新 Download 记录为“已完成” + Download update = new Download(); + update.setDownloadId(downloadId); + update.setState(exportSuccess ? 2 : -1); // 2 = 已完成 + update.setDownloadUrl(exportSuccess ? exportFileName : null); // 前端可通过此路径下载 + downloadMapper.updateById(update); + } + + /** + * 处理导出类型1:解析字段导出 + */ + private void handleExportParseData(ExportDataBoardDto dto, CSVWriter writer) { + List channels = dataBoardChannelMapper.selectList(new LambdaQueryWrapper() + .eq(DataBoardChannel::getDataBoardId, dto.getDataBoardId()) + .in(CollectionUtils.isNotEmpty(dto.getParam()), DataBoardChannel::getParameter, dto.getParam()) + .in(CollectionUtils.isNotEmpty(dto.getProtocolIds()), DataBoardChannel::getProtocolId, dto.getProtocolIds()) + ); + + List channelIds = channels.stream().map(DataBoardChannel::getChannelId).collect(Collectors.toList()); + List protocolIds = channels.stream().map(DataBoardChannel::getProtocolId).collect(Collectors.toList()); + + List dataList = channelDataMapper.selectList(new LambdaQueryWrapper() + .between(ChannelData::getTime, dto.getStartTime(), dto.getEndTime()) + .in(CollectionUtils.isNotEmpty(channelIds), ChannelData::getChannelId, channelIds) + .in(CollectionUtils.isNotEmpty(protocolIds), ChannelData::getProtocolId, protocolIds) + .orderByAsc(ChannelData::getTime) + ); + + // 构建映射关系 + Map channelIdToName = new HashMap<>(); + Map protocolIdToName = new HashMap<>(); + for (DataBoardChannel c : channels) { + channelIdToName.put(c.getChannelId(), c.getChannelName()); + protocolIdToName.put(c.getProtocolId(), c.getProtocolName()); + } + + // 写表头 +// writer.writeNext(new String[]{"通道", "协议", "数据字段", "源数据", "物理解析值", "物理单位", "时间"}); + + boolean headerHasWritten = false; + + List header = new ArrayList<>(); + List content = new ArrayList<>(); + // 写数据行 + for (ChannelData cd : dataList) { +// if (StringUtils.isBlank(cd.getData())) continue; +// List parseList = JSON.parseArray(cd.getData(), FiledParseData.class); +// for (FiledParseData fpd : parseList) { +// if (CollectionUtils.isNotEmpty(dto.getParam()) && !dto.getParam().contains(fpd.getReadName())) { +// continue; +// } +// String[] row = { +// orEmpty(channelIdToName.get(cd.getChannelId())), +// orEmpty(protocolIdToName.get(cd.getProtocolId())), +// orEmpty(fpd.getReadName()), +// orEmpty(fpd.getSourceValue()), +// orEmpty(fpd.getPhyValue()), +// orEmpty(fpd.getPhyUnit()), +// orEmpty(cd.getTime()) +// }; +// writer.writeNext(row); +// } + if (!headerHasWritten) { + header.add("时间"); + header.add("协议"); + header.add("通道"); + } + content.add(orEmpty(cd.getTime())); + content.add(orEmpty(protocolIdToName.get(cd.getProtocolId()))); + content.add(orEmpty(channelIdToName.get(cd.getChannelId()))); + + if (StringUtils.isBlank(cd.getData())) continue; + List parseList = JSON.parseArray(cd.getData(), FiledParseData.class); + for (FiledParseData fpd : parseList) { + if (CollectionUtils.isNotEmpty(dto.getParam()) && !dto.getParam().contains(fpd.getReadName())) { + continue; + } + if (!headerHasWritten) { + if (dto.getParseDataType() == 1 || dto.getParseDataType() == 3) { + header.add(fpd.getReadName() + "(源码)"); + } + if (dto.getParseDataType() == 2 || dto.getParseDataType() == 3) { + header.add(fpd.getReadName() + "(解析值)"); + } + } + if (dto.getParseDataType() == 1 || dto.getParseDataType() == 3) { + content.add(orEmpty(fpd.getSourceValue())); + } + if (dto.getParseDataType() == 2 || dto.getParseDataType() == 3) { + content.add(orEmpty(fpd.getPhyValue())); + } + } + + if (!headerHasWritten) { + headerHasWritten = true; + String[] headerArray = header.toArray(new String[0]); + writer.writeNext(headerArray); + header.clear(); + } + String[] contentArray = content.toArray(new String[0]); + writer.writeNext(contentArray); + content.clear(); + } + } + + /** + * 处理导出类型2:原始内容导出 + */ + private void handleExportSourceData(ExportDataBoardDto dto, File exportFile) throws IOException { + List channels = dataBoardChannelMapper.selectList(new LambdaQueryWrapper() + .eq(DataBoardChannel::getDataBoardId, dto.getDataBoardId()) + .in(CollectionUtils.isNotEmpty(dto.getParam()), DataBoardChannel::getParameter, dto.getParam()) + .in(CollectionUtils.isNotEmpty(dto.getProtocolIds()), DataBoardChannel::getProtocolId, dto.getProtocolIds()) + ); + + List channelIds = new ArrayList<>(); + + for (DataBoardChannel channel : channels) { + if (!channelIds.contains(channel.getChannelId())) { + channelIds.add(channel.getChannelId()); + } + } + + // 查询远程服务器数据 + List allData = new ArrayList<>(); + ChannelDataQueryAll query = new ChannelDataQueryAll(); + query.setChannelIds(channelIds); + query.setStartTime(dto.getStartTime()); + query.setEndTime(dto.getEndTime()); + + List servers = serverMapper.selectList(new LambdaQueryWrapper() + .eq(ServerInfo::getConnectStatus, 1) + .eq(ServerInfo::getIsDelete, "0") + ); + + for (ServerInfo server : servers) { + try { + String resp = postToServer(server, "/universaltestsoftware_server_api/outer/client/channelDataQueryAll", query); + if (StringUtils.isNotBlank(resp)) { + List list = JSON.parseArray(resp, ChannelDataHistoryVo.class); + allData.addAll(list); + } + } catch (Exception e) { + e.printStackTrace(); // 或记录日志 + } + } + + StringBuilder sb = new StringBuilder(); + for (ChannelDataHistoryVo vo : allData) { + sb.append(vo.getContentString()); + } + Files.write(Paths.get(exportFile.getPath()), sb.toString().getBytes(StandardCharsets.UTF_8)); + } + + /** + * 工具方法:null 转空字符串 + */ + private String orEmpty(String str) { + return str == null ? "" : str; + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DownloadServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DownloadServiceImpl.java new file mode 100644 index 0000000..9ce7007 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/DownloadServiceImpl.java @@ -0,0 +1,20 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.cbsd.universaltestsoftware_client.entity.Download; +import com.cbsd.universaltestsoftware_client.mapper.DownloadMapper; +import com.cbsd.universaltestsoftware_client.service.DownloadService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 服务实现类 + *

+ * + * @author admin + * @since 2026-02-03 + */ +@Service +public class DownloadServiceImpl extends ServiceImpl implements DownloadService { + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/InstructServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/InstructServiceImpl.java new file mode 100644 index 0000000..7b82814 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/InstructServiceImpl.java @@ -0,0 +1,1557 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.client.check.ICheckInterface; +import com.cbsd.client.pretreatment.CalculateUtils; +import com.cbsd.client.pretreatment.IPreTreatmentInterface; +import com.cbsd.universaltestsoftware_client.dto.*; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.InstructMapper; +import com.cbsd.universaltestsoftware_client.mapper.InvocationMapper; +import com.cbsd.universaltestsoftware_client.mapper.ProtocolMapper; +import com.cbsd.universaltestsoftware_client.mapper.ServerMapper; +import com.cbsd.universaltestsoftware_client.nettyclient.connection.ConnectionManager; +import com.cbsd.universaltestsoftware_client.nettyclient.entity.SendTcpContent; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import com.cbsd.universaltestsoftware_client.script.ScriptManager; +import com.cbsd.universaltestsoftware_client.service.InstructService; +import com.cbsd.universaltestsoftware_client.util.*; +import com.cbsd.universaltestsoftware_client.vo.BoardListVo; +import com.cbsd.universaltestsoftware_client.vo.ChannelListVo; +import com.cbsd.universaltestsoftware_client.vo.ServerListVo; +import com.cbsd.universaltestsoftware_client.websocket.command.CommandRunMessage; +import com.cbsd.universaltestsoftware_client.websocket.command.CommandWebSocketHandler; +import io.reactivex.rxjava3.core.Observable; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 指令服务实现类 + * 提供指令的增删改查、执行、排序、树形结构构建等功能 + */ +@Service +@Slf4j +public class InstructServiceImpl extends ServiceImpl implements InstructService { + + @Resource + private ServerMapper serverMapper; + + @Resource + private ConnectionManager connectionManager; + + @Resource + private ProtocolMapper protocolMapper; + + @Resource + private InvocationMapper invocationMapper; + + @Resource + private ChannelDataServiceImpl channelDataService; + + /** + * 指令文件路径 + */ + @Value("${cbsd.commandRootPath}") + private String commandRootPath; + + + /** + * 分页查询指令树 + */ + @Override + public Page pageList(InstructPageDto dto) { + Page page = baseMapper.selectPage(dto, new LambdaQueryWrapper() + .select(Instruct.class, i -> !i.getProperty().equals("protocolContent")) + .like(StringUtils.isNotBlank(dto.getKeyword()), Instruct::getInstructName, dto.getKeyword()) + .eq(Instruct::getInvocationId, dto.getInvocationId()) + .orderByAsc(Instruct::getSort)); + + // 构建树形结构 + List tree = buildTree(page.getRecords(), "0"); + page.setRecords(tree); + + return page; + } + + /** + * 执行指令:异步发送 TCP 报文 + */ + public Result execute(String instructId, CommandRunMessage runMessage) { + Instruct instruct = baseMapper.selectById(instructId); + if (instruct == null) return Result.error("指令不存在"); + + ServerInfo server = serverMapper.selectById(instruct.getServerId()); + if (server == null) { + log.info("指令执行失败, 指令名称: {}, 原因: 服务端不存在", instruct.getInstructName()); + return Result.error("服务端不存在"); + } + if (server.getConnectStatus() != 1) { + log.info("指令执行失败, 指令名称: {}, 原因: 服务端未连接", instruct.getInstructName()); + return Result.error("服务端未连接"); + } + //获取状态 + Map serverConnectionStatus = connectionManager.getServerConnectionStatus(); + + Boolean b = serverConnectionStatus.get(server.getServerId()); + if (b == null) { + log.info("指令执行失败, 指令名称: {}, 原因: 服务端以已断线", instruct.getInstructName()); + return Result.error("服务端以已断线"); + } + + String protocolXml = Optional.ofNullable(instruct.getProtocolId()) + .map(pid -> protocolMapper.selectById(pid)) + .map(Protocol::getProtocolContent) + .orElse(null); + + SendTcpContent.OperationType opType = + instruct.getOperationType() == 0 ? SendTcpContent.OperationType.DATA : + instruct.getOperationType() == 1 ? SendTcpContent.OperationType.REGISTER : null; + + // 异步发送 + doSend(instruct, server, protocolXml, opType, runMessage); + + return Result.success("指令已提交执行"); + } + + /** + * 真正发送逻辑,捕获异常并记录 + */ + private void doSend(Instruct instruct, ServerInfo server, String protocolXml, + SendTcpContent.OperationType opType, CommandRunMessage runMessage) { + try { + String command = instruct.getInstructText(); + String extraData = instruct.getExtraData(); + if (opType == SendTcpContent.OperationType.DATA) { + //操作类型为数据时 才处理指令数据文件和预处理脚本 + if (StringUtils.isBlank(command)) { + //判断指令文件是否为空 + if (StringUtils.isNotBlank(instruct.getInstructDataFile())) { + String dataFileContent = readInstructDataFile(instruct.getInstructDataFile()); + if (StringUtils.isNotBlank(dataFileContent)) { + command = dataFileContent; + } + } + } + //预处理脚本 + if (StringUtils.isNotBlank(command) && StringUtils.isNotBlank(instruct.getPreProcessScriptName())) { + String pretreatmentCommand = preTreatmentInstructCommand(command, instruct); + if (StringUtils.isNotBlank(pretreatmentCommand)) { + command = pretreatmentCommand; + } + } + } else if (opType == SendTcpContent.OperationType.REGISTER) { + //操作类型为寄存器 + // 20260304 特殊处理1553B板卡 添加消息和更新消息时 读取指令数据文件且预处理 复制给 额外参数中的data + if ("WT_PCI642_1553B".equals(instruct.getBoardTypeName()) && StringUtils.isNotBlank(instruct.getInstructDataFile())) { + //messageType=0&messageId=11&cmd1RtAddress=2&cmd1SubAddress=11&gapTime=3000000&reTry=true&isCycle=false&data=AAAA + Map extraDataMap = ConverUtils.convertStringToMap(instruct.getExtraData()); + if ((command.equals("bc_add_message") || command.equals("bc_message_update"))) { + if (extraDataMap.containsKey("data")) { + String extraDataData = extraDataMap.get("data"); + //读取指令数据文件 + String dataFileContent = readInstructDataFile(instruct.getInstructDataFile()); + if (StringUtils.isNotBlank(dataFileContent)) { + extraDataData = dataFileContent; + } + //预处理 + if (StringUtils.isNotBlank(instruct.getPreProcessScriptName())) { + String pretreatmentExtraData = preTreatmentInstructCommand(extraDataData, instruct); + if (StringUtils.isNotBlank(pretreatmentExtraData)) { + extraDataData = pretreatmentExtraData; + } + } + extraDataMap.put("data", extraDataData); + + } + } else if (command.equals("rt_start_transceiver_measurement_data")) { + //收发信机测量数据,读取指令数据文件 + String measureData = readInstructDataFile(instruct.getInstructDataFile()); + //预处理 + if (StringUtils.isNotBlank(measureData) && StringUtils.isNotBlank(instruct.getPreProcessScriptName())) { + String pretreatmentMeasureData = preTreatmentInstructCommand(measureData, instruct); + if (StringUtils.isNotBlank(pretreatmentMeasureData)) { + measureData = pretreatmentMeasureData; + } + } + extraDataMap.put("measureData", measureData); + } + extraData = ConverUtils.convertMapToString(extraDataMap); + } + + } + if (StringUtils.isNotBlank(command)) { + //操作类型为数据时判断指令数据是否需要拆分 + if (opType == SendTcpContent.OperationType.DATA && instruct.getDataSplittingLength() != null && instruct.getDataSplittingLength() > 0) { + List splitDataList = SplitUtils.splitStringByLength(command, instruct.getDataSplittingLength() * 2); + if (splitDataList.size() > 1) { + splitIndex = 0; + if (splitSend(splitDataList, instruct, server, protocolXml, opType, extraData)) { + afterSend(instruct, runMessage); + } + } else { + connectionManager.sendMessageToServer( + server.getServerId(), + command, + instruct.getChannelId(), + server.getTcpPort(), + opType, + protocolXml, + extraData); + afterSend(instruct, runMessage); + } + } else { + connectionManager.sendMessageToServer( + server.getServerId(), + command, + instruct.getChannelId(), + server.getTcpPort(), + opType, + protocolXml, + extraData); + afterSend(instruct, runMessage); + } + } else { + if (runMessage != null) { + //向前端推送 + runMessage.setInstruct(instruct); + runMessage.setCommandStatus(4); + broadcast(JSON.toJSONString(runMessage)); + } + } + + + } catch (Exception e) { + log.error("发送指令失败, 指令名称:{}, reason: {}", instruct.getInstructName(), e.getMessage()); + } + } + + /** + * 发送完成 + */ + private void afterSend(Instruct instruct, CommandRunMessage runMessage) { + try { + // 更新命中次数 + String hintTime = DateUtils.dateToString(); + UpdateWrapper uw = new UpdateWrapper<>(); + uw.eq("instruct_id", instruct.getInstructId()) + // 数据库层面的原子累加,避免丢失更新 + .setSql("hit_count = hit_count + 1") + .set("last_hit_time", hintTime); + baseMapper.update(null, uw); + + if (runMessage != null) { + //向前端推送 + instruct.setHitCount(instruct.getHitCount() + 1); + instruct.setLastHitTime(hintTime); + runMessage.setInstruct(instruct); + runMessage.setCommandStatus(3); + broadcast(JSON.toJSONString(runMessage)); + } + + //执行检查脚本 + if (StringUtils.isNotBlank(instruct.getCheckScriptName())) { + String originalCommand = instruct.getInstructText(); + if (StringUtils.isBlank(originalCommand)) { + //判断指令文件是否为空 + if (StringUtils.isNotBlank(instruct.getInstructDataFile())) { + File commandFile = new File(commandRootPath, instruct.getInstructDataFile()); + if (commandFile.exists()) { + try { + originalCommand = String.join("", Files.readAllLines(commandFile.toPath())); + } catch (Exception e) { + //按照二进制读取 + byte[] bytes = Files.readAllBytes(commandFile.toPath()); + originalCommand = DataUtils.byteToHex(bytes); + } + } + } + } + + Map latestDataMap = channelDataService.getChannelLatestData(instruct.getChannelId()); + + String preTreatmentCommand = originalCommand; + //获取预处理脚本 + if (StringUtils.isNotBlank(instruct.getPreProcessScriptName())) { + IPreTreatmentInterface preTreatmentInterface = (IPreTreatmentInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IPreTreatmentInterface.getValue(), instruct.getPreProcessScriptName()).newInstance(); + String pretreatmentResult = preTreatmentInterface.pretreatment(originalCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap, new CalculateUtils()); + if (StringUtils.isNotBlank(pretreatmentResult)) { + preTreatmentCommand = pretreatmentResult; + } + } + + ICheckInterface checkInterface = (ICheckInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.ICheckInterface.getValue(), instruct.getCheckScriptName()).newInstance(); + List logList = checkInterface.check(originalCommand, preTreatmentCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap); + if (logList != null && !logList.isEmpty()) { + for (String logInfo : logList) { + log.info(logInfo); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private int splitIndex = 0; + + /** + * 拆分发送指令 + */ + private boolean splitSend(List splitDataList, Instruct instruct, ServerInfo server, String protocolXml, + SendTcpContent.OperationType opType, String extraData) { + String hexMessage = splitDataList.get(splitIndex); + connectionManager.sendMessageToServer( + server.getServerId(), + hexMessage, + instruct.getChannelId(), + server.getTcpPort(), + opType, + protocolXml, + extraData); + if (instruct.getTailTime() != null && instruct.getTailTime() > 0) { + CountDownLatch latch = new CountDownLatch(1); + Observable.timer(instruct.getTailTime(), TimeUnit.MILLISECONDS).doOnComplete(latch::countDown).subscribe(); + try { + latch.await(); // 等待直到计时器完成或超时(如果有的话) + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (splitIndex < splitDataList.size() - 1) { + splitIndex++; + return splitSend(splitDataList, instruct, server, protocolXml, opType, extraData); + } + return true; + } + + /** + * 读取指令数据文件内容 + * + * @param instructDataFile 指令数据文件 + * @return 文件内容 + */ + private String readInstructDataFile(String instructDataFile) { + String fileContent = null; + File commandFile = new File(commandRootPath, instructDataFile); + if (commandFile.exists()) { + try { + fileContent = String.join("", Files.readAllLines(commandFile.toPath())); + } catch (Exception e) { + //按照二进制读取 + try { + byte[] bytes = Files.readAllBytes(commandFile.toPath()); + fileContent = DataUtils.byteToHex(bytes); + } catch (Exception ignored) { + + } + } + } + return fileContent; + } + + /** + * 预处理指令 + * + * @param originalCommand 原指令 + * @param instruct 指令对象 + * @return 处理后的内容 + */ + private String preTreatmentInstructCommand(String originalCommand, Instruct instruct) { + try { + IPreTreatmentInterface preTreatmentInterface = (IPreTreatmentInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IPreTreatmentInterface.getValue(), instruct.getPreProcessScriptName()).newInstance(); + //查询通道数据 + Map latestDataMap = channelDataService.getChannelLatestData(instruct.getChannelId()); + return preTreatmentInterface.pretreatment(originalCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap, new CalculateUtils()); + } catch (Exception e) { + return null; + } + } + + /** + * 获取所有已连接服务端及其板卡通道 + */ + @Override + public Result> getChannelList(String keyword) { + List servers = serverMapper.selectList( + new LambdaQueryWrapper() + .eq(ServerInfo::getIsDelete, "0") + .eq(ServerInfo::getConnectStatus, 1)); + ArrayList serverInfos = new ArrayList<>(); + //获取状态 + Map serverConnectionStatus = connectionManager.getServerConnectionStatus(); + servers.forEach(server -> { + //获取这个服务端是否连接成功,成功则加入 + if (CollectionUtils.isNotEmpty(serverConnectionStatus)) { + Boolean b = serverConnectionStatus.get(server.getServerId()); + if (b != null) { + if (b) { + serverInfos.add(server); + } + } + } + }); + + + List vos = serverInfos.parallelStream() + .map(server -> fetchBoardList(server, keyword)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + //取出所有板卡 + List serverListVos = new ArrayList<>(); + + for (ServerListVo vo : vos) { + ServerListVo serverListVo = new ServerListVo(); + serverListVo.setServerId(vo.getServerId()); + serverListVo.setServerName(vo.getServerName()); + serverListVo.setIpAddress(vo.getIpAddress()); + serverListVo.setTcpPort(vo.getTcpPort()); + serverListVo.setHttpPort(vo.getHttpPort()); + ArrayList newBoardListVo = new ArrayList<>(); + List boardListVos = vo.getBoardListVos(); + for (BoardListVo boardListVo : boardListVos) { + List boardListVos1 = boardListVo.getBoardListVos(); + if (CollectionUtils.isNotEmpty(boardListVos1)) { + newBoardListVo.add(boardListVo); + } + } + serverListVo.setBoardListVos(newBoardListVo); + serverListVos.add(serverListVo); + + } + return Result.success(serverListVos); + } + + /** + * 远程调用服务端获取板卡列表 + */ + private ServerListVo fetchBoardList(ServerInfo server, String keyword) { + try { + String url = String.format("http://%s:%s/universaltestsoftware_server_api/outer/client/pageListBoardAndChannel", + server.getIpAddress(), server.getHttpPort()); + Map hashMap = new HashMap<>(); + hashMap.put("keyWord", keyword); + hashMap.put("onlineState", 1); + + String resp = HttpUtil.get(url, hashMap); + if (StringUtils.isBlank(resp)) return null; + + Result result = JSON.parseObject(resp, Result.class); + if (result.getCode() == 1) { + ServerListVo vo = new ServerListVo(); + BeanUtils.copyProperties(server, vo); + vo.setBoardListVos(JSON.parseArray(result.getData().toString(), BoardListVo.class)); + return vo; + } + } catch (Exception e) { + log.warn("获取板卡列表异常,serverId={}", server.getServerId(), e); + } + return null; + } + + /** + * 获取通道驱动函数名 + */ + @Override + public String getChannelFunctionName(String channelId, String serverId) { + ServerInfo server = serverMapper.selectById(serverId); + if (server == null) throw new RuntimeException("服务端不存在"); + + String url = String.format("http://%s:%s/universaltestsoftware_server_api/common/getBoardDriverRegisterFunctions", + server.getIpAddress(), server.getHttpPort()); + Map param = Collections.singletonMap("channelId", channelId); + try { + String resp = HttpUtil.get(url, param); + if (StringUtils.isBlank(resp)) throw new RuntimeException("连接失败"); + return resp; + } catch (Exception e) { + log.error("获取驱动函数名异常", e); + throw new RuntimeException("连接失败"); + } + } + + /** + * 保存或更新指令组(树形编码) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Result saveOrUpdateInstructGroup(InstructGroupDto dto) { + if (StringUtils.isBlank(dto.getInstructId())) { + return createInstructGroup(dto); + } else { + Instruct exist = baseMapper.selectById(dto.getInstructId()); + if (exist == null) return Result.error("指令组不存在"); + exist.setInstructName(dto.getInstructName()); + exist.setSort(dto.getSort()); + try { + baseMapper.updateById(exist); + } catch (Exception e) { + if (e instanceof DuplicateKeyException) { + return Result.error("同一用例内指令名称重复"); + } + return Result.error("保存失败"); + } + return Result.success(); + } + } + + /** + * 新建指令组:自动生成编码、排序 + */ + private Result createInstructGroup(InstructGroupDto dto) { + Instruct instruct = new Instruct(); + instruct.setInstructName(dto.getInstructName()); + instruct.setSort(dto.getSort()); + instruct.setInvocationId(dto.getInvocationId()); + instruct.setInstructType(dto.getInstructType()); + Instruct olInstruct = null; + //最后一条数据的sort + int sort = nextRootSort(dto.getInvocationId()); + //判断是否新增在某一条指令的后面 + if (StringUtils.isNotBlank(dto.getInferiorId())) { + //查询当前得sort + olInstruct = baseMapper.selectById(dto.getInferiorId()); + if (olInstruct != null) { + instruct.setSort(olInstruct.getSort() + 1); + if (StringUtils.isEmpty(dto.getParentEncoding())) { + if (!olInstruct.getParentEncoding().equals("0")) { + dto.setParentEncoding(olInstruct.getParentEncoding()); + } + + } + + } else { + instruct.setSort(sort); + } + } else { + instruct.setSort(sort); + } + + if (StringUtils.isBlank(dto.getParentEncoding())) { + // 根节点 + String maxCode = getMaxEncoding("0", dto.getInvocationId()); + instruct.setEncoding(nextCode(maxCode)); + instruct.setParentEncoding("0"); + + if (instruct.getInstructType() == 1) { + instruct.setInstructName("新指令" + instruct.getEncoding()); + } else { + instruct.setInstructName("新指令组" + instruct.getEncoding()); + } + instruct.setInstructTag(instruct.getEncoding()); + } else { + // 子节点 + if (!existsEncoding(dto.getParentEncoding(), dto.getInvocationId())) { + return Result.error("父级编码不存在"); + } + String parent = dto.getParentEncoding(); + String maxCode = getMaxEncoding(parent, dto.getInvocationId()); + //001001 + instruct.setEncoding(parent + nextCode(maxCode)); + instruct.setParentEncoding(parent); + instruct.setParentName(getNameByEncoding(parent, dto.getInvocationId())); + + if (instruct.getInstructType() == 1) { + instruct.setInstructName("新指令" + instruct.getEncoding()); + } else { + instruct.setInstructName("新指令组" + instruct.getEncoding()); + } + instruct.setInstructTag(instruct.getEncoding()); + } + + try { + baseMapper.insert(instruct); + if (olInstruct != null) { + //大于等于它得sort+1,并且不等他它本身。 + baseMapper.shiftSortAfter(instruct.getInvocationId(), instruct.getSort(), instruct.getInstructId()); + } + //如果是新增指令组,在后面新增一条指令 + if (instruct.getInstructType() == 2) { + InstructGroupDto instructGroupDto = new InstructGroupDto(); + instructGroupDto.setInvocationId(instruct.getInvocationId()); + instructGroupDto.setInstructType(1); + instructGroupDto.setInferiorId(instruct.getInstructId()); + instructGroupDto.setParentEncoding(instruct.getEncoding()); + createInstructGroup(instructGroupDto); + } + } catch (Exception e) { + if (e instanceof DuplicateKeyException) { + return Result.error("同一用例内指令名称重复"); + } + return Result.error("保存失败"); + } + reOrder(dto.getInvocationId()); + return Result.success(); + } + + /** + * 更新指令 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public Result saveOrUpdateInstruct(Instruct instruct) { + if (StringUtils.isBlank(instruct.getInstructId())) { + throw new BaseException("指令id不能为空"); + } + if (StringUtils.isNotBlank(instruct.getProtocolId())) { + Protocol protocol = protocolMapper.selectById(instruct.getProtocolId()); + if (protocol != null) { + instruct.setProtocolName(protocol.getProtocolName()); + } + } + try { + baseMapper.updateById(instruct); + if (StringUtils.isNotBlank(instruct.getInstructName())) { + instruct = baseMapper.selectById(instruct.getInstructId()); + if (instruct.getInstructType() == 2) { + //指令组修改时,将组内的父级名字更改 + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getParentName, instruct.getInstructName()) + .eq(Instruct::getInvocationId, instruct.getInvocationId()) + .eq(Instruct::getParentEncoding, instruct.getEncoding()) + ); + } + } + + Integer status = instruct.getStatus(); + if (status != null) { + //修改的状态 + instruct = baseMapper.selectById(instruct.getInstructId()); + if (instruct.getInstructType() == 2) { + //指令组修改状态时,将组内的指令状态同步修改 + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getStatus, status) + .eq(Instruct::getInvocationId, instruct.getInvocationId()) + .likeRight(Instruct::getParentEncoding, instruct.getEncoding())); + } + } + + } catch (Exception e) { + if (e instanceof DuplicateKeyException) { + return Result.error("同一用例内指令名称重复"); + } + throw new BaseException("保存失败"); + } + reOrder(instruct.getInvocationId()); + return Result.success(); + } + + @Override + public Result moveUp(String instructId) { + Instruct current = baseMapper.selectById(instructId); + if (current == null || current.getSort() == null) { + return Result.error("指令不存在"); + } + Instruct prev = baseMapper.findPrevSibling(current.getSort(), current.getParentEncoding(), current.getInvocationId()); + if (prev == null) { + return Result.error("已经是第一个不能上移"); + } + + // 交换 sort + int tempSort = current.getSort(); + current.setSort(prev.getSort()); + prev.setSort(tempSort); + + baseMapper.updateSort(current); + baseMapper.updateSort(prev); + + reOrder(current.getInvocationId()); + + return Result.success(); + + } + + @Override + public Result moveDown(String instructId) { + Instruct current = baseMapper.selectById(instructId); + if (current == null || current.getSort() == null) { + return Result.error("指令不存在"); + } + + Instruct next = baseMapper.findNextSibling(current.getSort(), current.getParentEncoding(), current.getInvocationId()); + if (next == null) { + return Result.error("已经是最后一个不能下移"); // 已经是最后一个 + } + + // 交换 sort + int tempSort = current.getSort(); + current.setSort(next.getSort()); + next.setSort(tempSort); + + baseMapper.updateSort(current); + baseMapper.updateSort(next); + + reOrder(current.getInvocationId()); + + return Result.success(); + } + + /** + * 删除指令及其子节点 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Result deleteInstructById(InstructDeleteDto dto) { + if (dto == null) { + return Result.error("参数不能为空"); + } + if (StringUtils.isBlank(dto.getInvocationId())) { + return Result.error("用例id不能为空"); + } + if (CollectionUtils.isEmpty(dto.getInstructIds())) { + return Result.error("请选择要删除的指令"); + } + + // 2. 删除 + List instructs = baseMapper.selectList(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, dto.getInvocationId()) + .in(Instruct::getInstructId, dto.getInstructIds())); + baseMapper.deleteBatchIds(dto.getInstructIds()); + for (Instruct instruct : instructs) { + if (instruct.getInstructType() == 2) { + baseMapper.delete(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, dto.getInvocationId()) + .likeRight(Instruct::getParentEncoding, instruct.getEncoding())); + } else { + Long l = baseMapper.selectCount(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, dto.getInvocationId()) + .eq(Instruct::getParentEncoding, instruct.getParentEncoding())); + if (l == 0) { + baseMapper.delete(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, dto.getInvocationId()) + .eq(Instruct::getEncoding, instruct.getParentEncoding())); + } + } + } + // 3. 该 invocationId 下剩余节点全局重排 1,2,3... + baseMapper.reorderGlobalSortContinuous(dto.getInvocationId()); + return Result.success(); + } + + /** + * 获取板卡驱动额外数据 + */ + @Override + public String getBoardDriverExtraData(String channelId, String serverId) { + ServerInfo server = serverMapper.selectById(serverId); + if (server == null) throw new BaseException("服务端不存在"); + + String url = String.format("http://%s:%s/universaltestsoftware_server_api/common/getBoardDriverExtraData", + server.getIpAddress(), server.getHttpPort()); + try { + String resp = HttpUtil.get(url, Collections.singletonMap("channelId", channelId)); + if (StringUtils.isBlank(resp)) throw new RuntimeException("连接失败"); + return resp; + } catch (Exception e) { + log.error("获取驱动额外数据异常", e); + throw new RuntimeException("连接失败"); + } + } + + /** + * 解析协议参数 + */ + @Override + public Result> getInstructProtocolParam(String instructId) { + Instruct instruct = baseMapper.selectById(instructId); + if (instruct == null) throw new BaseException("指令不存在"); + + Protocol protocol = protocolMapper.selectById(instruct.getProtocolId()); + if (protocol == null || StringUtils.isBlank(protocol.getProtocolContent())) { + return Result.success(Collections.emptyList()); + } + + try { + ProtocolXml xml = XmlToObjectConverter.convertXmlToObject(protocol.getProtocolContent(), ProtocolXml.class); + if (xml.getFileds() != null && !xml.getFileds().isEmpty()) { + List list = xml.getFileds(); + list.sort(Comparator.comparingInt(Filed::getSnInt)); + return Result.success(list); + } + } catch (Exception e) { + log.error("协议解析失败,instructId={}", instructId, e); + } + return Result.success(Collections.emptyList()); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public void createGroupFromList(CreateGroupDTO dto) { + /* 1. 校验:存在 + 同 invocationId */ + List roots = baseMapper.selectByIdsAndInvocation(dto.getInstructIds(), dto.getInvocationId()); + if (roots.size() < 2) throw new BaseException("至少选择 2 个节点"); + //如果他们是同级 + String parentEncoding = roots.get(0).getParentEncoding(); + boolean TJ = false; + long count = roots.stream().filter(e -> e.getParentEncoding().equals(parentEncoding)).count(); + if (count == roots.size()) { + TJ = true; + } + //查询所有的组id。 + List collect = roots.stream().filter(e -> e.getInstructType() == 2).map(Instruct::getEncoding).distinct().collect(Collectors.toList()); + //存在这个的组id编码移除 + roots.removeIf(e -> collect.contains(e.getParentEncoding())); + + /* 2. 创建新组(根级) */ + String parentEnc; + if (TJ) { + parentEnc = parentEncoding; + } else { + parentEnc = "0"; + } + String newGrpCode; + if (parentEnc.equals("0")) { + newGrpCode = nextCode(getMaxEncoding(parentEnc, dto.getInvocationId())); + } else { + newGrpCode = parentEnc + nextCode(getMaxEncoding(parentEnc, dto.getInvocationId())); + } + + int newGrpSort = getMaxSort(parentEnc, dto.getInvocationId()) + 1; + + Instruct group = new Instruct(); + group.setInstructType(2); + group.setInstructName("新组" + newGrpSort); + group.setInvocationId(dto.getInvocationId()); + group.setParentEncoding(parentEnc); + group.setParentName("无"); + group.setEncoding(newGrpCode); + group.setInstructTag(newGrpCode); + group.setSort(newGrpSort); + group.setStatus(1); + baseMapper.insert(group); + + for (Instruct root : roots) { + root.setParentEncoding(newGrpCode); + root.setParentName(group.getInstructName()); + baseMapper.updateById(root); + } + reOrder(dto.getInvocationId()); + } + + /** + * 重新排序 + * + * @param invocationId 用例id + */ + private void reOrder(String invocationId) { + List instructList = baseMapper.selectList(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, invocationId)); + // 构建树形结构 + List tree = buildTree(instructList, "0"); + + List saveList = new ArrayList<>(); + for (Instruct instruct : tree) { + saveList.add(instruct); + saveList.addAll(getInstructChildren(instruct)); + } + for (int i = 0; i < saveList.size(); i++) { + saveList.get(i).setSort(i + 1); + } + //保存 + if (!saveList.isEmpty()) { + saveOrUpdateBatch(saveList); + } + } + + private List getInstructChildren(Instruct instruct) { + List resultList = new ArrayList<>(); + List childList = instruct.getChildren(); + if (childList != null && !childList.isEmpty()) { + for (Instruct child : childList) { + resultList.add(child); + resultList.addAll(getInstructChildren(child)); + } + } + return resultList; + } + + + /** + * 批量复制 + * + * @param dto + */ + @Override + public void batchCopyBelow(BatchCopyBelowDTO dto) { + /* 1. 校验存在 & 同 invocationId */ + List roots = baseMapper.selectByIdsAndInvocation(dto.getInstructIds(), dto.getInvocationId()); + if (roots.size() != dto.getInstructIds().size()) + throw new BaseException("存在无效节点或跨用例"); + + //查询所有的组id。 + List collect = roots.stream().filter(e -> e.getInstructType() == 2).map(Instruct::getEncoding).distinct().collect(Collectors.toList()); + //存在这个的组id编码移除 + roots.removeIf(e -> collect.contains(e.getParentEncoding())); + + /* 3. 逐节点复制(单指令 or 整棵组) */ + for (Instruct src : roots) { + if (src.getInstructType() == 1) { + // 单指令:复制自身 + copySingleBelow(src, dto.getInvocationId()); + } else { + // 整组:复制根 + 完整子树 + copyWholeTreeBelow(src, dto.getInvocationId()); + } + } + reOrder(dto.getInvocationId()); + /* 4. 整个 invocationId 全局重排 1,2,3… */ +// baseMapper.reorderGlobalSortContinuous(dto.getInvocationId()); + + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void batchMoveBelow(BatchMoveBelowDTO dto) { + if (CollectionUtils.isEmpty(dto.getInstructs())) { + throw new BaseException("移动数据不能为空"); + } + //取出他的id + List ids = dto.getInstructs().stream().map(SortDto::getInstructId).collect(Collectors.toList()); + List roots = baseMapper.selectByIdsAndInvocation(ids, dto.getInvocationId()); + if (roots.size() != ids.size()) + throw new BaseException("存在无效节点或跨用例"); + //查询所有的组id。 + List collect = roots.stream().filter(e -> e.getInstructType() == 2).map(Instruct::getEncoding).distinct().collect(Collectors.toList()); + //存在这个的组id编码移除 + roots.removeIf(e -> collect.contains(e.getParentEncoding())); + + for (Instruct root : roots) { + Integer oldSort = root.getSort(); + //查询出他移动的序号 + int sort = dto.getInstructs().stream().filter(e -> e.getInstructId().equals(root.getInstructId())).findFirst().get().getSort(); + //查出他原来的序号是什么 + Instruct oldInstruct = baseMapper.selectOne(new LambdaQueryWrapper().eq(Instruct::getInvocationId, dto.getInvocationId()).eq(Instruct::getSort, sort).last("limit 1")); + if (oldInstruct != null) { + String oldEncoding = root.getEncoding(); + //更换他的父级 + root.setParentName(oldInstruct.getParentName()); + root.setParentEncoding(oldInstruct.getParentEncoding()); + //重新生成编码 + if (root.getParentEncoding().equals("0")) { + root.setEncoding(nextCode(getMaxEncoding(root.getParentEncoding(), dto.getInvocationId()))); + } else { + root.setEncoding(root.getParentEncoding() + nextCode(getMaxEncoding(root.getParentEncoding(), dto.getInvocationId()))); + } + //他的子集更换的父级 + UpdateWrapper instructUpdateWrapper = new UpdateWrapper<>(); + instructUpdateWrapper.set("parent_encoding", root.getEncoding()) + .set("parent_name", root.getInstructName()) + .eq("parent_encoding", oldEncoding); + + baseMapper.update(null, instructUpdateWrapper); + + + root.setSort(sort); + baseMapper.updateById(root); + //往后移,区间进一个不含本身 + if (sort > oldSort) { + baseMapper.minusSort(dto.getInvocationId(), oldSort, sort, root.getInstructId()); + } + //往前移动,移动到的位置后的人全部+1 + if (sort < oldSort) { + //区间的sort +1 + baseMapper.plusSort(dto.getInvocationId(), oldSort, sort, root.getInstructId()); + } + } + } + + reOrder(dto.getInvocationId()); + } + + private void copySingleBelow(Instruct src, String invocationId) { + Instruct copy = cloneNode(src); + //查询一边移动后sort + Instruct instruct = baseMapper.selectById(src.getInstructId()); + if (instruct == null) { + return; + } + copy.setSort(instruct.getSort() + 1); // 紧挨下方 + copy.setParentEncoding(src.getParentEncoding()); + copy.setParentName(src.getParentName()); + if (copy.getParentEncoding().equals("0")) { + copy.setEncoding(nextCode(getMaxEncoding(src.getParentEncoding(), invocationId))); + } else { + copy.setEncoding(copy.getParentEncoding() + nextCode(getMaxEncoding(src.getParentEncoding(), invocationId))); + } + copy.setInstructTag(copy.getEncoding()); + baseMapper.insert(copy); + + baseMapper.shiftSortAfter(copy.getInvocationId(), copy.getSort(), copy.getInstructId()); + } + + private void copyWholeTreeBelow(Instruct root, String invocationId) { + /* 1) 先复制根 */ + Instruct rootCopy = cloneNode(root); + //查询一边移动后sort + Instruct instruct = baseMapper.selectById(root.getInstructId()); + if (instruct == null) { + return; + } + rootCopy.setSort(instruct.getSort() + 1); + + rootCopy.setParentEncoding(root.getParentEncoding()); + rootCopy.setParentName(root.getParentName()); + + if (rootCopy.getParentEncoding().equals("0")) { + rootCopy.setEncoding(nextCode(getMaxEncoding(rootCopy.getParentEncoding(), invocationId))); + } else { + rootCopy.setEncoding(rootCopy.getParentEncoding() + nextCode(getMaxEncoding(rootCopy.getParentEncoding(), invocationId))); + } + rootCopy.setInstructTag(rootCopy.getEncoding()); + baseMapper.insert(rootCopy); + + baseMapper.shiftSortAfter(rootCopy.getInvocationId(), rootCopy.getSort(), rootCopy.getInstructId()); + /* 2) 递归复制子树(保留相对顺序) */ + copySubTreeRecursively(root.getEncoding(), rootCopy.getEncoding(), invocationId, rootCopy.getInstructName()); + } + + /** + * 递归复制子树:只改 parent 头缀 & 编码头缀,保留 sort 相对值 + * + * @param oldPrefix 原编码头缀(如 040) + * @param newPrefix 新编码头缀(如 007) + */ + private void copySubTreeRecursively(String oldPrefix, String newPrefix, String invocationId, String parentName) { + // 直接子孙:parentEncoding == oldPrefix + List children = baseMapper.listByParentEncodingAndInvocation(invocationId, oldPrefix); + for (Instruct child : children) { + Instruct copy = cloneNode(child); + copy.setParentEncoding(newPrefix); + copy.setSort(nextRootSort(invocationId)); + if (copy.getParentEncoding().equals("0")) { + copy.setEncoding(nextCode(getMaxEncoding(copy.getParentEncoding(), invocationId))); + } else { + copy.setEncoding(copy.getParentEncoding() + nextCode(getMaxEncoding(copy.getParentEncoding(), invocationId))); + } + copy.setInstructTag(copy.getEncoding()); + copy.setParentName(parentName); + baseMapper.insert(copy); + + // 递归复制下一层 + copySubTreeRecursively(child.getEncoding(), copy.getEncoding(), invocationId, copy.getInstructName()); + } + } + + + @Override + public Result batchChangeParam(BatchChangeInstructParamDto dto) { + if (dto == null) { + return Result.error("参数不能为空"); + } + if (StringUtils.isBlank(dto.getInvocationId()) && (dto.getInstructIdList() == null || dto.getInstructIdList().isEmpty())) { + return Result.error("用例id或指令id不能为空"); + } + if (dto.getStatus() != null && dto.getStatus() != 1 && dto.getStatus() != 0) { + return Result.error("状态错误"); + } + if (dto.getIsDebug() != null && dto.getIsDebug() != 1 && dto.getIsDebug() != 0) { + return Result.error("断点状态错误"); + } + if (dto.getLeadTime() != null && dto.getLeadTime() < 0) { + return Result.error("前置等待时间不能小于0"); + } + if (dto.getTailTime() != null && dto.getTailTime() < 0) { + return Result.error("后置等待时间不能小于0"); + } + + if (dto.getStatus() == null && dto.getIsDebug() == null && dto.getLeadTime() == null && dto.getTailTime() == null) { + return Result.error("改变参数不能为空"); + } + try { + if (StringUtils.isNotBlank(dto.getInvocationId())) { + //根据用例id批量修改 + baseMapper.update(null, new LambdaUpdateWrapper() + .set(dto.getStatus() != null, Instruct::getStatus, dto.getStatus()) + .set(dto.getIsDebug() != null, Instruct::getBreakPoint, dto.getIsDebug()) + .set(dto.getLeadTime() != null, Instruct::getLeadTime, dto.getLeadTime()) + .set(dto.getTailTime() != null, Instruct::getTailTime, dto.getTailTime()) + .eq(Instruct::getInvocationId, dto.getInvocationId())); + } else { + //根据指令id批量修改 + baseMapper.update(null, new LambdaUpdateWrapper() + .set(dto.getStatus() != null, Instruct::getStatus, dto.getStatus()) + .set(dto.getIsDebug() != null, Instruct::getBreakPoint, dto.getIsDebug()) + .set(dto.getLeadTime() != null, Instruct::getLeadTime, dto.getLeadTime()) + .set(dto.getTailTime() != null, Instruct::getTailTime, dto.getTailTime()) + .in(Instruct::getInstructId, dto.getInstructIdList())); + } + } catch (Exception e) { + throw new BaseException("修改失败"); + } + return Result.success(); + } + + /** + * 获取用例列表 + * + * @param invocationIdList 用例id列表 + */ + public List getInvocationListByIds(List invocationIdList) { + if (invocationIdList == null || invocationIdList.isEmpty()) { + return Collections.emptyList(); + } + return invocationMapper.selectList(new LambdaQueryWrapper().in(Invocation::getInvocationId, invocationIdList) + .orderByAsc(Invocation::getSort)); + } + + /** + * 根据用例id获取指令列表 + * + * @param invocationId 用例id + */ + public List getInstructListByInvocationId(String invocationId) { + if (StringUtils.isBlank(invocationId)) { + return null; + } + return baseMapper.selectList(new LambdaQueryWrapper().eq(Instruct::getInvocationId, invocationId) + .orderByAsc(Instruct::getSort)); + } + + /** + * 根据id列表获取指令列表 + * + * @param instructIdList 指令id列表 + */ + public List getInstructListByIds(List instructIdList) { + return baseMapper.selectList(new LambdaQueryWrapper().in(Instruct::getInstructId, instructIdList) + .orderByAsc(Instruct::getSort)); + } + + /** + * 重置指令命中次数 + * + * @param invocationIdList 指令id列表 + */ + public void resetHitCountByInstructIdList(List invocationIdList) { + if (invocationIdList == null || invocationIdList.isEmpty()) { + return; + } + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getHitCount, 0) + .in(Instruct::getInstructId, invocationIdList)); + } + + /** + * 重置指令命中次数 + * + * @param invocationId 用例id + */ + public void resetHitCountByInvocation(String invocationId) { + if (StringUtils.isBlank(invocationId)) { + return; + } + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getHitCount, 0) + .eq(Instruct::getInvocationId, invocationId)); + } + + @Override + public Result uploadCommandFile(String instructId, MultipartFile file) { + if (StringUtils.isBlank(instructId)) { + return Result.error("指令id不能为空"); + } + if (file == null) { + return Result.error("文件不能为空"); + } + String fileName = file.getOriginalFilename(); + if (StringUtils.isBlank(fileName)) { + return Result.error("文件名不能为空"); + } + Instruct instruct = baseMapper.selectById(instructId); + if (instruct == null) { + return Result.error("指令不存在"); + } + File parentFile = new File(commandRootPath); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + String suffix = fileName.substring(fileName.lastIndexOf(".")); + if (!".txt".equals(suffix) && !".dat".equals(suffix)) { + return Result.error("请上传文本文件(.txt)或dat(.dat)文件"); + } + if (instruct.getInstructType() == 1) { + //常规指令 + String saveFileName = instruct.getInstructId() + suffix; + File existsFile = new File(parentFile, saveFileName); + if (existsFile.exists()) { + existsFile.delete(); + } + try { + Path savePath = Paths.get(parentFile.getPath(), saveFileName); + Files.write(savePath, file.getBytes()); + } catch (Exception e) { + throw new BaseException(e.getMessage()); + } + try { + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getInstructDataFile, saveFileName) + .eq(Instruct::getInstructId, instructId)); + } catch (Exception e) { + if (existsFile.exists()) { + existsFile.delete(); + } + return Result.error("上传失败 " + e.getMessage()); + } + } else if (instruct.getInstructType() == 2) { + //指令组,指令组下所有指令统一处理 + List childInstructList = baseMapper.selectList(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, instruct.getInvocationId()) + .eq(Instruct::getInstructType, 1) + .likeRight(Instruct::getParentEncoding, instruct.getEncoding())); + if (childInstructList != null && !childInstructList.isEmpty()) { + for (Instruct childInstruct : childInstructList) { + String saveFileName = instruct.getInstructId() + suffix; + File existsFile = new File(parentFile, saveFileName); + if (existsFile.exists()) { + existsFile.delete(); + } + try { + Path savePath = Paths.get(parentFile.getPath(), saveFileName); + Files.write(savePath, file.getBytes()); + } catch (Exception ignored) { + + } + try { + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getInstructDataFile, saveFileName) + .eq(Instruct::getInstructId, childInstruct.getInstructId())); + } catch (Exception e) { + if (existsFile.exists()) { + existsFile.delete(); + } + } + } + } else { + return Result.error("该指令组下无指令"); + } + } + return Result.success(); + } + + @Override + public void uploadScript(String instructId, String scriptType, String scriptFile) { + if (StringUtils.isBlank(instructId)) { + throw new BaseException("指令id不能为空"); + } + if (StringUtils.isBlank(scriptType)) { + throw new BaseException("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(scriptType)) { + throw new BaseException("脚本类型错误"); + } + if (StringUtils.isBlank(scriptFile)) { + throw new BaseException("脚本文件不能为空"); + } + Instruct instruct = baseMapper.selectById(instructId); + if (instruct == null) { + throw new BaseException("指令不存在"); + } + String suffix = scriptFile.substring(scriptFile.lastIndexOf(".")); + String scriptName = scriptFile.replace(suffix, ""); + try { + baseMapper.update(null, new LambdaUpdateWrapper() + .set(scriptType.equals(ScriptManager.ScriptType.IPreTreatmentInterface.getValue()), Instruct::getPreProcessScriptFile, scriptFile) + .set(StringUtils.isBlank(instruct.getPreProcessScript()) && scriptType.equals(ScriptManager.ScriptType.IPreTreatmentInterface.getValue()), Instruct::getPreProcessScriptName, scriptName) + .set(scriptType.equals(ScriptManager.ScriptType.IBranchConditionInterface.getValue()), Instruct::getBranchConditionScriptFile, scriptFile) + .set(StringUtils.isBlank(instruct.getBranchConditionScript()) && scriptType.equals(ScriptManager.ScriptType.IBranchConditionInterface.getValue()), Instruct::getBranchConditionScriptName, scriptName) + .set(scriptType.equals(ScriptManager.ScriptType.ICheckInterface.getValue()), Instruct::getCheckScriptFile, scriptFile) + .set(StringUtils.isBlank(instruct.getCheckScript()) && scriptType.equals(ScriptManager.ScriptType.ICheckInterface.getValue()), Instruct::getCheckScriptName, scriptName) + .eq(Instruct::getInstructId, instructId)); + } catch (Exception e) { + throw new BaseException(e.getMessage()); + } + } + + @Override + public void uploadScriptContent(String instructId, String scriptType, String scriptFileName, String scriptContent) { + if (StringUtils.isBlank(instructId)) { + throw new BaseException("指令id不能为空"); + } + if (StringUtils.isBlank(scriptType)) { + throw new BaseException("脚本类型不能为空"); + } + if (!ScriptManager.ScriptType.getValues().contains(scriptType)) { + throw new BaseException("脚本类型错误"); + } + Instruct instruct = baseMapper.selectById(instructId); + if (instruct == null) { + throw new BaseException("指令不存在"); + } + try { + if (scriptType.equals(ScriptManager.ScriptType.IPreTreatmentInterface.getValue())) { + String preProcessScriptFile = instruct.getPreProcessScriptFile(); + String tempPreProcessScriptFileName = StringUtils.isNotBlank(preProcessScriptFile) ? preProcessScriptFile.replace(".java", "") : null; + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getPreProcessScript, StringUtils.isNotBlank(scriptContent) ? scriptContent : null) + .set(StringUtils.isNotBlank(scriptContent), Instruct::getPreProcessScriptName, scriptFileName) + .set(StringUtils.isBlank(scriptContent), Instruct::getPreProcessScriptName, tempPreProcessScriptFileName) + .eq(Instruct::getInstructId, instructId)); + } else if (scriptType.equals(ScriptManager.ScriptType.IBranchConditionInterface.getValue())) { + String branchConditionScriptFile = instruct.getBranchConditionScriptFile(); + String tempBranchConditionScriptFileName = StringUtils.isNotBlank(branchConditionScriptFile) ? branchConditionScriptFile.replace(".java", "") : null; + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getBranchConditionScript, StringUtils.isNotBlank(scriptContent) ? scriptContent : null) + .set(StringUtils.isNotBlank(scriptContent), Instruct::getBranchConditionScriptName, scriptFileName) + .set(StringUtils.isBlank(scriptContent), Instruct::getBranchConditionScriptName, tempBranchConditionScriptFileName) + .eq(Instruct::getInstructId, instructId)); + } else if (scriptType.equals(ScriptManager.ScriptType.ICheckInterface.getValue())) { + String checkScriptFile = instruct.getCheckScriptFile(); + String tempCheckScriptFileName = StringUtils.isNotBlank(checkScriptFile) ? checkScriptFile.replace(".java", "") : null; + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Instruct::getCheckScript, StringUtils.isNotBlank(scriptContent) ? scriptContent : null) + .set(StringUtils.isNotBlank(scriptContent), Instruct::getCheckScriptName, scriptFileName) + .set(StringUtils.isBlank(scriptContent), Instruct::getCheckScriptName, tempCheckScriptFileName) + .eq(Instruct::getInstructId, instructId)); + } + } catch (Exception e) { + throw new BaseException(e.getMessage()); + } + } + + @Override + public Result deleteInstructFile(InstructDeleteFileDto dto) { + if (dto == null) { + return Result.error("参数不能为空"); + } + if (StringUtils.isBlank(dto.getInstructId())) { + return Result.error("指令id不能为空"); + } + if (dto.getDeleteFileType() == null) { + return Result.error("删除文件类型不能为空"); + } + try { + baseMapper.update(null, new LambdaUpdateWrapper() + .set(dto.getDeleteFileType() == 1, Instruct::getPreProcessScriptFile, null) + .set(dto.getDeleteFileType() == 2, Instruct::getBranchConditionScriptFile, null) + .set(dto.getDeleteFileType() == 3, Instruct::getCheckScriptFile, null) + .set(dto.getDeleteFileType() == 4, Instruct::getInstructDataFile, null) + .set(dto.getDeleteFileType() == 5, Instruct::getProtocolId, null) + .set(dto.getDeleteFileType() == 5, Instruct::getProtocolName, null) + .eq(Instruct::getInstructId, dto.getInstructId())); + } catch (Exception e) { + return Result.error("删除失败"); + } + return Result.success("删除成功"); + } + + @Override + public String getInstructDataRemark(String instructId) { + if (StringUtils.isBlank(instructId)) { + throw new BaseException("指令id不能为空"); + } + Instruct instruct = baseMapper.selectById(instructId); + if (instruct == null) { + throw new BaseException("指令不存在"); + } + if (StringUtils.isBlank(instruct.getChannelId())) { + throw new BaseException("该指令为选择通道"); + } + ServerInfo server = serverMapper.selectById(instruct.getServerId()); + String url = String.format("http://%s:%s/universaltestsoftware_server_api/common/getBoardDriverSendDataAnnotation", + server.getIpAddress(), server.getHttpPort()); + Map param = Collections.singletonMap("channelId", instruct.getChannelId()); + return HttpUtil.get(url, param); + } + + /** + * 将扁平的节点列表转换为树形结构,并按sort字段排序 + * + * @param nodes 所有节点的列表 + * @param rootParentEncoding 根节点的父编码标识,通常为空字符串或特定标识 + * @return 树形结构的根节点列表 + */ + public List buildTree(List nodes, String rootParentEncoding) { + // 存储所有节点的编码与节点对象的映射,便于快速查找 + Map nodeMap = new HashMap<>(); + + // 首先将所有节点放入map中 + for (Instruct node : nodes) { + nodeMap.put(node.getEncoding(), node); + } + + // 根节点列表 + List rootNodes = new ArrayList<>(); + + // 遍历所有节点,构建树形结构 + for (Instruct node : nodes) { + String parentEncoding = node.getParentEncoding(); + + // 如果是根节点 + if (rootParentEncoding.equals(parentEncoding)) { + rootNodes.add(node); + } else { + // 查找父节点并添加到父节点的子节点列表中 + Instruct parentNode = nodeMap.get(parentEncoding); + if (parentNode != null) { + parentNode.getChildren().add(node); + } else { + // 如果父节点不存在,也将其作为根节点处理 + rootNodes.add(node); + } + } + } + + // 对整棵树进行排序 + sortTree(rootNodes); + + return rootNodes; + } + + /** + * 递归对树形结构进行排序 + * + * @param nodes 节点列表 + */ + private void sortTree(List nodes) { + // 先对当前层级的节点按sort字段排序 + nodes.sort(Comparator.comparingInt(Instruct::getSort)); + + // 递归对每个节点的子节点进行排序 + for (Instruct node : nodes) { + if (!node.getChildren().isEmpty()) { + sortTree(node.getChildren()); + } + } + } + + /* ---------- 以下为辅助方法 ---------- */ + + + private boolean existsEncoding(String encoding, String invocationId) { + return baseMapper.selectCount( + new LambdaQueryWrapper() + .eq(Instruct::getEncoding, encoding) + .eq(Instruct::getInvocationId, invocationId)) > 0; + } + + private String getMaxEncoding(String parentEncoding, String invocationId) { + Instruct last = baseMapper.selectOne( + new LambdaQueryWrapper() + .eq(Instruct::getParentEncoding, parentEncoding) + .eq(Instruct::getInvocationId, invocationId) + .orderByDesc(Instruct::getEncoding) + .last("limit 1")); + return last == null ? null : last.getEncoding(); + } + + /** + * 根据同级最大编码,生成下一个编码 + * 自动根据提取出的数字长度确定格式化宽度 + * + * @param maxCode 最大编码,如 "001001" 或 "ABC001009" + * @return 下一个编码,如 "001002" + */ + private String nextCode(String maxCode) { + if (maxCode == null || maxCode.trim().isEmpty()) { + return "001"; + } + + // 1. 提取字符串中的所有数字 + String digits = maxCode.replaceAll("\\D+", ""); + + if (digits.isEmpty()) { + throw new IllegalArgumentException("编码中不包含数字: " + maxCode); + } + + // 2. 解析为 long 并加 1 + long nextNumber; + try { + long currentNumber = Long.parseLong(digits); + nextNumber = currentNumber + 1; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("无法解析编码中的数字: " + digits, e); + } + + // 3. 转换为字符串并截取或补充至三位数 + String nextCode = String.format("%03d", nextNumber); + + // 4. 确保总是返回三位数 + return nextCode.substring(nextCode.length() - Math.min(nextCode.length(), 3)); + } + + private int nextRootSort(String invocationId) { + Instruct last = baseMapper.selectOne( + new LambdaQueryWrapper() +// .eq(Instruct::getParentEncoding, "0") + .eq(Instruct::getInvocationId, invocationId) + .orderByDesc(Instruct::getSort) + .last("limit 1")); + return last == null ? 1 : last.getSort() + 1; + } + + private String getNameByEncoding(String encoding, String invocationId) { + return baseMapper.selectOne( + new LambdaQueryWrapper() + .eq(Instruct::getEncoding, encoding) + .eq(Instruct::getInvocationId, invocationId) + .select(Instruct::getInstructName)).getInstructName(); + } + + private int getMaxSort(String parentEncoding, String invocationId) { + Instruct last = baseMapper.selectOne(new LambdaQueryWrapper() +// .eq(Instruct::getParentEncoding, parentEncoding) + .eq(Instruct::getInvocationId, invocationId) + .orderByDesc(Instruct::getSort).last("limit 1")); + return last == null ? 0 : last.getSort(); + } + + + private Instruct cloneNode(Instruct src) { + Instruct tar = new Instruct(); + BeanUtils.copyProperties(src, tar); + tar.setInstructId(null); // 自增 + tar.setInstructName(src.getInstructName() + "_副本"); + tar.setHitCount(0); + tar.setLastHitTime(null); + return tar; + } + + + // 方案运行开始 + + @Resource + private CommandWebSocketHandler commandWebSocketHandler; + + /** + * 发送指令 + * + * @param runMessage 运行指令消息 + */ + public Result sendInstruct(CommandRunMessage runMessage) { + return execute(runMessage.getInstruct().getInstructId(), runMessage); + } + + public void broadcast(String message) { + commandWebSocketHandler.broadcast(message); + } + + // 方案运行结束 +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/InvocationServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/InvocationServiceImpl.java new file mode 100644 index 0000000..2501fa7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/InvocationServiceImpl.java @@ -0,0 +1,367 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.dto.InstructItem; +import com.cbsd.universaltestsoftware_client.dto.InstructXmlDTO; +import com.cbsd.universaltestsoftware_client.dto.InvocationPageDto; +import com.cbsd.universaltestsoftware_client.dto.InvocationSortDto; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.InstructMapper; +import com.cbsd.universaltestsoftware_client.mapper.InvocationMapper; +import com.cbsd.universaltestsoftware_client.script.ScriptManager; +import com.cbsd.universaltestsoftware_client.service.InstructService; +import com.cbsd.universaltestsoftware_client.service.InvocationService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.UUIDUtils; +import com.cbsd.universaltestsoftware_client.vo.InstructScriptVo; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + *

+ * 用列 服务实现类 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@Service +public class InvocationServiceImpl extends ServiceImpl implements InvocationService { + + @Resource + private InstructMapper instructMapper; + + @Resource + private ScriptManager scriptManager; + + @Resource + private InstructService instructService; + + @Override + public Page pageList(InvocationPageDto invocationPageDto) { + Page serveBasePage = baseMapper.selectPage(invocationPageDto, new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(invocationPageDto.getKeyword()), Invocation::getInvocationName, invocationPageDto.getKeyword()) + .eq(Invocation::getSchemeId, invocationPageDto.getSchemeId()) + .orderByAsc(Invocation::getSort) + ); + return serveBasePage; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Result sort(InvocationSortDto dto) { + if (dto == null) { + return Result.error("参数不能为空"); + } + if (StringUtils.isBlank(dto.getSchemeId())) { + return Result.error("方案id不能为空"); + } + if (StringUtils.isBlank(dto.getInvocationId())) { + return Result.error("用例id不能为空"); + } + if (dto.getSort() == null) { + return Result.error("排序不能为空"); + } + Invocation invocation = baseMapper.selectById(dto.getInvocationId()); + if (invocation == null) { + return Result.error("用例不存在"); + } + int oldSort = invocation.getSort(); + int newSort = dto.getSort(); + long count = baseMapper.selectCount(new LambdaQueryWrapper() + .eq(Invocation::getSchemeId, dto.getSchemeId())); + if (newSort < 1) { + newSort = 1; + } + if (newSort > count) { + newSort = Math.toIntExact(count); + } + try { + if (newSort > oldSort) { + //小于等于newSort 大于oldSort 的sort - 1 + baseMapper.subtractSort(dto.getSchemeId(), newSort, oldSort); + } else if (newSort < oldSort) { + //大于等于newSort 小于oldSort的sort+1 + baseMapper.addSort(dto.getSchemeId(), newSort, oldSort); + } + baseMapper.update(null, new LambdaUpdateWrapper() + .set(Invocation::getSort, newSort) + .eq(Invocation::getInvocationId, dto.getInvocationId())); + } catch (Exception e) { + throw new BaseException("移动失败"); + } + return Result.success(); + } + + @Override + public void exportInvocation(String invocationId, HttpServletResponse response) throws IOException { + Invocation invocation = baseMapper.selectById(invocationId); + if (invocation == null) { + throw new BaseException("用例不存在"); + } + //查询下面得所有指令 + List instructs = instructMapper.selectList(new LambdaQueryWrapper().eq(Instruct::getInvocationId, invocationId)); + //组装数据 + ArrayList instructItems = new ArrayList(); + for (Instruct instruct : instructs) { + InstructItem instructItem = new InstructItem(); + BeanUtils.copyProperties(instruct, instructItem); + instructItem.setInvocationName(invocation.getInvocationName()); + instructItems.add(instructItem); + } + + + try { + InstructXmlDTO instructXmlDTO = new InstructXmlDTO(instructItems); + + + String xmlContent = marshalToXml(instructXmlDTO); + + // 🔽 安全处理文件名(防止中文乱码) + String encodedFilename = URLEncoder.encode(invocation.getInvocationName(), "UTF-8"); + // 兼容 IE/Edge(可选) + String headerValue = String.format("attachment; filename=\"%s\"; filename*=UTF-8''%s", + encodedFilename, encodedFilename); + + // 🔽 设置响应头 + response.setContentType("application/xml;charset=UTF-8"); + response.setHeader("Content-Disposition", headerValue); + response.setContentLength(xmlContent.getBytes("UTF-8").length); + + // 输出 XML 字符串 + response.getWriter().write(xmlContent); + response.getWriter().flush(); + + } catch (Exception e) { + e.printStackTrace(); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write("Server error: " + e.getMessage() + ""); + response.getWriter().flush(); + } + + + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void importXml(MultipartFile file, String schemeId) throws Exception { + if (file.isEmpty()) { + throw new IllegalArgumentException("上传文件为空"); + } + + if (!Objects.requireNonNull(file.getOriginalFilename()).toLowerCase().endsWith(".xml")) { + throw new IllegalArgumentException("仅支持 XML 文件"); + } + + List items; + try (InputStream is = file.getInputStream()) { + InstructXmlDTO dto = unmarshal(is); + if (dto == null || dto.getInstructs() == null || dto.getInstructs().isEmpty()) { + throw new IllegalArgumentException("XML 中未找到有效指令数据"); + } + items = dto.getInstructs(); + } + + // 获取所有指令中的用例名(应一致) + String invocationName = file.getOriginalFilename(); + if (StringUtils.isBlank(invocationName)) { + throw new BaseException("用例文件名称不能为空"); + } + String suffix = invocationName.substring(invocationName.lastIndexOf(".")); + invocationName = invocationName.replace(suffix, ""); + + // 查询或创建 Invocation(用例) + Invocation invocation = baseMapper.selectOne(new LambdaQueryWrapper().eq(Invocation::getInvocationName, invocationName) + .eq(Invocation::getSchemeId, schemeId)); + + String invocationId; + if (invocation == null) { + // 新建用例 + long count = baseMapper.selectCount(new LambdaQueryWrapper().eq(Invocation::getSchemeId, schemeId)); + invocation = new Invocation(); + invocation.setInvocationId(UUIDUtils.getUUID()); + invocation.setInvocationName(invocationName); + invocation.setSchemeId(schemeId); + invocation.setSort(Math.toIntExact(count + 1)); + invocation.setCreateTime(DateUtils.dateToString()); + baseMapper.insert(invocation); + invocationId = invocation.getInvocationId(); + } else { + invocationId = invocation.getInvocationId(); + + //删除原来的指令 + instructMapper.delete(new LambdaQueryWrapper() + .eq(Instruct::getInvocationId, invocationId)); + } + + // 批量保存指令 + List instructList = new ArrayList<>(); + for (InstructItem item : items) { + Instruct instruct = new Instruct(); + BeanUtils.copyProperties(item, instruct); + instruct.setInstructId(UUIDUtils.getUUID()); + instruct.setInvocationId(invocationId); // 关联到当前用例 + + //处理脚本 + String fixed = "public class "; + String implementsStr = "implements"; + //1.预处理脚本 + String preProcessScript = item.getPreProcessScript(); + if (StringUtils.isNotBlank(preProcessScript)) { + //获取原类名 + try { + int fixedIndex = preProcessScript.indexOf(fixed) + fixed.length(); + int implementsIndex = preProcessScript.indexOf(implementsStr); + String originalClassName = preProcessScript.substring(fixedIndex, implementsIndex - 1); + preProcessScript = preProcessScript.replace(originalClassName, "Pretreatment_" + instruct.getInstructId()); + InstructScriptVo vo = scriptManager.saveScriptContent(instruct.getInstructId(), "pretreatment", preProcessScript); + + instruct.setPreProcessScript(preProcessScript); + instruct.setPreProcessScriptName(vo.getScriptName()); + } catch (Exception e) { + instruct.setPreProcessScript(null); + instruct.setPreProcessScriptName(null); + } + } else { + instruct.setPreProcessScript(null); + instruct.setPreProcessScriptName(null); + } + //2.分支条件脚本 + String branchConditionScript = item.getBranchConditionScript(); + if (StringUtils.isNotBlank(branchConditionScript)) { + //获取原类名 + try { + int fixedIndex = branchConditionScript.indexOf(fixed) + fixed.length(); + int implementsIndex = branchConditionScript.indexOf(implementsStr); + String originalClassName = branchConditionScript.substring(fixedIndex, implementsIndex - 1); + branchConditionScript = branchConditionScript.replace(originalClassName, "Branch_" + instruct.getInstructId()); + InstructScriptVo vo = scriptManager.saveScriptContent(instruct.getInstructId(), "branch", branchConditionScript); + + instruct.setBranchConditionScript(branchConditionScript); + instruct.setBranchConditionScriptName(vo.getScriptName()); + } catch (Exception e) { + instruct.setBranchConditionScript(null); + instruct.setBranchConditionScriptName(null); + } + } else { + instruct.setBranchConditionScript(null); + instruct.setBranchConditionScriptName(null); + } + //3.检查脚本 + String checkScript = item.getCheckScript(); + if (StringUtils.isNotBlank(checkScript)) { + //获取原类名 + try { + int fixedIndex = checkScript.indexOf(fixed) + fixed.length(); + int implementsIndex = checkScript.indexOf(implementsStr); + String originalClassName = checkScript.substring(fixedIndex, implementsIndex - 1); + checkScript = checkScript.replace(originalClassName, "Check_" + instruct.getInstructId()); + InstructScriptVo vo = scriptManager.saveScriptContent(instruct.getInstructId(), "check", checkScript); + + instruct.setCheckScript(checkScript); + instruct.setCheckScriptName(vo.getScriptName()); + } catch (Exception e) { + instruct.setCheckScript(null); + instruct.setCheckScriptName(null); + } + } else { + instruct.setCheckScript(null); + instruct.setCheckScriptName(null); + } + + if (instruct.getOperationType() == null) { + instruct.setOperationType(0); + } + if (instruct.getLeadTime() == null) { + instruct.setLeadTime(0); + } + if (instruct.getTailTime() == null) { + instruct.setTailTime(1000); + } + if (instruct.getBreakPoint() == null) { + instruct.setBreakPoint(0); + } + if (instruct.getDataSplittingLength() == null) { + instruct.setDataSplittingLength(0); + } + instructList.add(instruct); + } + // 批量插入或更新(根据主键判断) + if (!instructList.isEmpty()) { + int batchSize = 1000; + int total = instructList.size(); + if (total > batchSize) { + int insertTimes = total / batchSize; + int lastSize = total % batchSize; + if (lastSize > 0) { + insertTimes += 1; + } + for (int i = 0; i < insertTimes; i++) { + List insertList; + if (lastSize > 0 && i == insertTimes - 1) { + insertList = instructList.subList(i * batchSize, i * batchSize + lastSize); + } else { + insertList = instructList.subList(i * batchSize, i * batchSize + batchSize); + } + instructMapper.insertBatchSomeColumn(insertList); + } + } else { + instructMapper.insertBatchSomeColumn(instructList); + } + } + } + + @Override + public Result deleteById(String invocationId) { + Invocation invocation = baseMapper.selectById(invocationId); + if (invocation == null) { + return Result.error("用例不存在"); + } + baseMapper.deleteById(invocationId); + instructMapper.delete(new LambdaQueryWrapper().eq(Instruct::getInvocationId, invocationId)); + //将该用例后的用例序号减一 + baseMapper.unificationSubtractSort(invocation.getSchemeId(), invocation.getSort()); + return Result.success(); + } + + private String marshalToXml(InstructXmlDTO dto) throws JAXBException { + JAXBContext context = JAXBContext.newInstance(InstructXmlDTO.class); + Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); + + StringWriter writer = new StringWriter(); + marshaller.marshal(dto, writer); + return writer.toString(); + } + + private InstructXmlDTO unmarshal(InputStream inputStream) throws JAXBException { + JAXBContext context = JAXBContext.newInstance(InstructXmlDTO.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return (InstructXmlDTO) unmarshaller.unmarshal(inputStream); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ParserProtocolServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ParserProtocolServiceImpl.java new file mode 100644 index 0000000..c347226 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ParserProtocolServiceImpl.java @@ -0,0 +1,22 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.entity.ParserProtocol; +import com.cbsd.universaltestsoftware_client.service.ParserProtocolService; +import com.cbsd.universaltestsoftware_client.mapper.ParserProtocolMapper; +import org.springframework.stereotype.Service; + +/** +* @author SanJie +* @description 针对表【parser_protocol(解析器协议)】的数据库操作Service实现 +* @createDate 2025-09-04 15:56:52 +*/ +@Service +public class ParserProtocolServiceImpl extends ServiceImpl + implements ParserProtocolService{ + +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ParserServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ParserServiceImpl.java new file mode 100644 index 0000000..b7e69b7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ParserServiceImpl.java @@ -0,0 +1,176 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.entity.*; +import com.cbsd.universaltestsoftware_client.mapper.ParserMapper; +import com.cbsd.universaltestsoftware_client.mapper.ParserProtocolMapper; +import com.cbsd.universaltestsoftware_client.mapper.ProtocolMapper; +import com.cbsd.universaltestsoftware_client.parser.ParserManager; +import com.cbsd.universaltestsoftware_client.service.ParserService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author SanJie + * @description 针对表【parser(解析器)】的数据库操作Service实现 + * @createDate 2025-09-04 15:55:48 + */ +@Service +public class ParserServiceImpl extends ServiceImpl implements ParserService { + + @Resource + private ParserProtocolMapper parserProtocolMapper; + + @Resource + private ParserManager parserManager; + + @Resource + private ProtocolMapper protocolMapper; + + @Resource + private ProtocolServiceImpl protocolService; + + @Override + public Result> getParserList(String channelId) { + LambdaQueryWrapper eq = new LambdaQueryWrapper().eq(Parser::getChannelId, channelId) + .orderByAsc(Parser::getParserSort); + List parserList = this.list(eq); + //查询有几个协议 + for (Parser parser : parserList) { + Long l = parserProtocolMapper.selectCount(new LambdaQueryWrapper().eq(ParserProtocol::getParserId, parser.getParserId())); + + parser.setProtocolCount(l); + } + return Result.success(parserList); + } + + @Override + public Result saveOrUpdateParser(Parser parser) { + if (StringUtils.isBlank(parser.getParserId())) { + parser.setCreateTime(DateUtils.dateToString()); + long count = baseMapper.selectCount(new LambdaQueryWrapper().eq(Parser::getChannelId, parser.getChannelId())); + parser.setParserSort((int) (count + 1)); + boolean save = this.save(parser); + if (save) { + return Result.success("保存成功"); + } else { + return Result.error("保存失败"); + } + } else { + boolean update = this.updateById(parser); + if (update) { + return Result.success("修改成功"); + } else { + return Result.error("修改失败"); + } + } + } + + @Override + public Result deleteParserById(String parserId) { + //查询通道 + Parser parser = this.getById(parserId); + if (parser != null) { + boolean remove = this.removeById(parserId); + if (remove) { + parserManager.refresh(); + } + parserProtocolMapper.delete(new LambdaQueryWrapper().eq(ParserProtocol::getParserId, parserId)); + + //对该通道解析器重新排序 + List parserList = baseMapper.selectList(new LambdaQueryWrapper().eq(Parser::getChannelId, parser.getChannelId()) + .orderByAsc(Parser::getParserSort)); + if (parserList != null && !parserList.isEmpty()) { + for (int i = 0; i < parserList.size(); i++) { + parserList.get(i).setParserSort(i + 1); + } + saveOrUpdateBatch(parserList); + } + updateDataBoard(parser); + } + return Result.success(); + } + + @Override + public Result addProtocol(List parserProtocol) { + if (CollectionUtils.isNotEmpty(parserProtocol)) { + //先删除 + String parserId = parserProtocol.get(0).getParserId(); + + parserProtocolMapper.delete(new LambdaQueryWrapper().eq(ParserProtocol::getParserId, parserId)); + for (ParserProtocol protocol : parserProtocol) { + protocol.setCreateTime(DateUtils.dateToString()); + parserProtocolMapper.insert(protocol); + } + Parser parser = baseMapper.selectById(parserId); + if (parser != null) { + parserManager.refresh(); + updateDataBoard(parser); + } + } + + return Result.success("添加成功"); + } + + @Override + public Result> getProtocolList(String parserId) { + List parserProtocolList = parserProtocolMapper.selectList(new LambdaQueryWrapper().eq(ParserProtocol::getParserId, parserId)); + List collect = parserProtocolList.stream().map(ParserProtocol::getProtocolId).distinct().collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(collect)) { + List protocols = protocolMapper.selectList(new LambdaQueryWrapper().in(Protocol::getProtocolId, collect)); + for (ParserProtocol parserProtocol : parserProtocolList) { + //设置协议名字 + parserProtocol.setProtocolName(protocols.stream().filter(protocol -> protocol.getProtocolId().equals(parserProtocol.getProtocolId())).findFirst().get().getProtocolName()); + + + } + } + return Result.success(parserProtocolList); + } + + @Override + public Result deleteParserProtocolById(String parserProtocolId) { + ParserProtocol parserProtocol = parserProtocolMapper.selectById(parserProtocolId); + if (parserProtocol != null) { + int delete = parserProtocolMapper.deleteById(parserProtocolId); + if (delete != 0) { + Parser parser = baseMapper.selectById(parserProtocol.getParserId()); + if (parser != null) { + parserManager.refresh(); + updateDataBoard(parser); + } + } + + } + return Result.success("删除成功"); + } + + //更新数据看板参数 + private void updateDataBoard(Parser parser) { + /* 3. 找到这些通道关联的看板-通道关系 & 看板 */ + List channelIds = new ArrayList(); + channelIds.add(parser.getChannelId()); + List boardChannels = protocolService.listBoardChannelsByChannelIds(channelIds); + List dataBoards = protocolService.listDataBoardsByChannels(boardChannels); + if (dataBoards.isEmpty()) { + return; + } + /* 4. 逐一看板同步到遥测 */ + for (DataBoard board : dataBoards) { + protocolService.syncSingleBoard(board, boardChannels); + } + } +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ProtocolServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ProtocolServiceImpl.java new file mode 100644 index 0000000..003e424 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ProtocolServiceImpl.java @@ -0,0 +1,399 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelReader; +import com.alibaba.excel.read.listener.PageReadListener; +import com.alibaba.excel.read.metadata.ReadSheet; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.dto.ChannelDto; +import com.cbsd.universaltestsoftware_client.dto.InsertDataBoardDto; +import com.cbsd.universaltestsoftware_client.dto.ProtocolImport; +import com.cbsd.universaltestsoftware_client.dto.ProtocolPageDto; +import com.cbsd.universaltestsoftware_client.entity.*; +import com.cbsd.universaltestsoftware_client.mapper.*; +import com.cbsd.universaltestsoftware_client.parser.ParserManager; +import com.cbsd.universaltestsoftware_client.parser.enums.ProtocolEnum; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.parser.model.Meta; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import com.cbsd.universaltestsoftware_client.parser.model.VariableLengthDesc; +import com.cbsd.universaltestsoftware_client.service.DataBoardService; +import com.cbsd.universaltestsoftware_client.service.ProtocolService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.UUIDUtils; +import com.cbsd.universaltestsoftware_client.util.XmlToObjectConverter; +import com.cbsd.universaltestsoftware_client.vo.ProtocolSaveVo; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.xml.bind.JAXBException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 协议 服务实现类 + *

+ * + * @author admin + * @since 2025-09-02 + */ +@Service +public class ProtocolServiceImpl extends ServiceImpl implements ProtocolService { + + @Resource + private ParserProtocolMapper parserProtocolMapper; + + @Resource + private ParserMapper parserMapper; + + @Resource + private ParserManager parserManager; + + @Resource + private DataBoardChannelMapper dataBoardChannelMapper; + + @Resource + private DataBoardMapper dataBoardMapper; + + @Resource + private DataBoardService dataBoardService; + + private String defaultProtocol = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + @Override + public Result> pageListProtocol(ProtocolPageDto dto) { + BasePage protocolBasePage = baseMapper.selectPage(dto, new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(dto.getKeyword()), Protocol::getProtocolName, dto.getKeyword()) + .eq(dto.getProtocolType() != null, Protocol::getProtocolType, dto.getProtocolType()) + .orderByAsc(Protocol::getCreateTime) + ); + return Result.success(protocolBasePage); + } + + @Override + public Result deleteProtocolById(String protocolId) { + if (removeById(protocolId)) { + refreshParserManager(); + parserProtocolMapper.delete(new LambdaQueryWrapper().eq(ParserProtocol::getProtocolId, protocolId)); + //查询通道通道 + return Result.success(); + } + return Result.error("删除失败"); + } + + @Override + public Result saveOrUpdateProtocol(Protocol protocol) { + if (StringUtils.isNotBlank(protocol.getProtocolId())) { + try { + ProtocolXml protocolXml = XmlToObjectConverter.convertXmlToObject(protocol.getProtocolContent(), ProtocolXml.class); + if (protocolXml.getFileds() != null) { + int totalBitsLen = 0; + for (Filed filed : protocolXml.getFileds()) { + totalBitsLen += filed.getBitsLen(); + } + if (totalBitsLen % 8 != 0) { + return Result.error("协议字段bitslen总长度必须为8的倍数"); + } + } + } catch (Exception e) { + return Result.error("协议内容不合法"); + } + protocol.setUpdateTime(DateUtils.dateToString()); + baseMapper.updateById(protocol); + //修改遥测那边参数 + updateTelemetry(protocol.getProtocolId()); + } else { + protocol.setCreateTime(DateUtils.dateToString()); + protocol.setProtocolContent(defaultProtocol); + baseMapper.insert(protocol); + } + protocol = baseMapper.selectById(protocol.getProtocolId()); + ProtocolSaveVo vo = new ProtocolSaveVo(); + vo.setProtocol(protocol); + try { + vo.setParse(XmlToObjectConverter.convertXmlToObject(protocol.getProtocolContent(), ProtocolXml.class)); + } catch (JAXBException e) { + e.printStackTrace(); + } + refreshParserManager(); + return Result.success(vo); + } + + private void refreshParserManager() { + parserManager.refresh(); + } + + /** + * 协议参数发生变更后,把受影响的数据看板及其通道同步到遥测系统 + * 注意:本方法不处理事务,由调用方决定是否需要事务 + * + * @param protocolId 协议主键 + */ + private void updateTelemetry(String protocolId) { + /* 1. 找到所有关联的解析器 ID */ + List parserIds = getParserIdsByProtocol(protocolId); + if (parserIds.isEmpty()) { + return; + } + + /* 2. 找到解析器对应的所有通道 ID */ + List channelIds = getChannelIdsByParsers(parserIds); + if (channelIds.isEmpty()) { + return; + } + + /* 3. 找到这些通道关联的看板-通道关系 & 看板 */ + List boardChannels = listBoardChannelsByChannelIds(channelIds); + List dataBoards = listDataBoardsByChannels(boardChannels); + if (dataBoards.isEmpty()) { + return; + } + + /* 4. 逐一看板同步到遥测 */ + for (DataBoard board : dataBoards) { + syncSingleBoard(board, boardChannels); + } + } + + /* ====================== 抽取的私有方法 ====================== */ + + /** + * 根据协议 ID 拿到所有解析器 ID(已去重) + */ + private List getParserIdsByProtocol(String protocolId) { + return parserProtocolMapper + .selectList(new LambdaQueryWrapper() + .eq(ParserProtocol::getProtocolId, protocolId)) + .stream() + .map(ParserProtocol::getParserId) + .distinct() + .collect(Collectors.toList()); + } + + /** + * 根据解析器 ID 列表拿到所有通道 ID(已去重) + */ + private List getChannelIdsByParsers(List parserIds) { + return parserMapper + .selectList(new LambdaQueryWrapper() + .in(Parser::getParserId, parserIds)) + .stream() + .map(Parser::getChannelId) + .distinct() + .collect(Collectors.toList()); + } + + /** + * 根据通道 ID 列表拿到看板-通道关联列表 + */ + public List listBoardChannelsByChannelIds(List channelIds) { + return dataBoardChannelMapper + .selectList(new LambdaQueryWrapper() + .in(DataBoardChannel::getChannelId, channelIds)); + } + + /** + * 根据看板-通道关联列表拿到需要去重的看板列表 + */ + public List listDataBoardsByChannels(List boardChannels) { + List boardIds = boardChannels + .stream() + .map(DataBoardChannel::getDataBoardId) + .distinct() + .collect(Collectors.toList()); + if (boardIds.isEmpty()) { + return Collections.emptyList(); + } + return dataBoardMapper + .selectList(new LambdaQueryWrapper() + .in(DataBoard::getDataBoardId, boardIds)); + } + + /** + * 同步单块看板及其通道到遥测系统 + */ + public void syncSingleBoard(DataBoard board, List boardChannels) { + try { + // 属于当前看板的通道 +// List channelsOfBoard = boardChannels +// .stream() +// .filter(bc -> bc.getDataBoardId().equals(board.getDataBoardId())) +// .collect(Collectors.toList()); + //通过数据看板id查询所有通道 + List channelsOfBoard = dataBoardChannelMapper.selectList(new LambdaQueryWrapper(). + eq(DataBoardChannel::getDataBoardId, board.getDataBoardId()) + .groupBy(DataBoardChannel::getChannelId) + ); + + // 按 channelId 去重,并直接收集成 channelList + List channelDtos = channelsOfBoard + .stream() + .map(channel -> { + ChannelDto channelDto = new ChannelDto(); + BeanUtils.copyProperties(channel, channelDto); + return channelDto; + }) + .distinct() + .collect(Collectors.toList()); + + InsertDataBoardDto dto = new InsertDataBoardDto(); + BeanUtils.copyProperties(board, dto); + dto.setChannelList(channelDtos); + + // 调用远程遥测服务 + dataBoardService.saveOrUpdateDataBoard(dto); + } catch (Exception e) { + // 打印日志 & 继续同步下一块看板,也可换成抛业务异常 + } + } + + @Override + public Result importProtocol(MultipartFile file) { + try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) { + //基本信息 + List basicInfoList = new ArrayList<>(); + //字段信息 + List filedList = new ArrayList<>(); + //可变长度字段信息 + List variableLengthList = new ArrayList<>(); + + ReadSheet basicInfoSheet = + EasyExcel.readSheet(0).head(ProtocolImport.BasicInfo.class).registerReadListener(new PageReadListener(basicInfoList::addAll)).headRowNumber(2).build(); + ReadSheet filedSheet = + EasyExcel.readSheet(1).head(ProtocolImport.Filed.class).registerReadListener(new PageReadListener(filedList::addAll)).headRowNumber(2).build(); + ReadSheet variableLengthSheet = + EasyExcel.readSheet(2).head(ProtocolImport.VariableLength.class).registerReadListener(new PageReadListener(variableLengthList::addAll)).headRowNumber(2).build(); + excelReader.read(basicInfoSheet, filedSheet, variableLengthSheet); + + if (basicInfoList.isEmpty()) { + return Result.error("请填写协议基本信息"); + } + if (basicInfoList.size() > 1) { + return Result.error("请填写一个协议基本信息"); + } + if (filedList.isEmpty()) { + return Result.error("请填写字段信息"); + } + + ProtocolXml protocolXml = new ProtocolXml(); + ProtocolImport.BasicInfo basicInfo = basicInfoList.get(0); + if (StringUtils.isBlank(basicInfo.getReadName())) { + return Result.error("基本信息readName为空"); + } + protocolXml.setReadName(basicInfo.getReadName()); + Meta meta = new Meta(); + meta.setFrameHeadValue(basicInfo.getFrameHeadValue()); + meta.setFrameTailValue(basicInfo.getFrameTailValue()); + meta.setCheckStartSn(basicInfo.getCheckStartSn()); + meta.setCheckEndSn(basicInfo.getCheckEndSn()); + meta.setVerifyScript(basicInfo.getVerifyScript()); + if (!variableLengthList.isEmpty()) { + List variableLengthDescList = variableLengthList.stream().map(it -> { + VariableLengthDesc variableLengthDesc = new VariableLengthDesc(); + variableLengthDesc.setValue(it.getValue()); + variableLengthDesc.setLength(it.getLength()); + return variableLengthDesc; + }).collect(Collectors.toList()); + meta.setVariableLengthDescList(variableLengthDescList); + } + protocolXml.setMeta(meta); + String[] phyTypeArr = new String[]{"int", "unsigned int", "float", "double", "hex", "time"}; + List fileds = new ArrayList<>(); + for (int i = 0; i < filedList.size(); i++) { + int row = i + 2; + ProtocolImport.Filed importFiled = filedList.get(i); + importFiled.setSn(String.valueOf(i)); + importFiled.setLabel("filed" + i); + if (StringUtils.isBlank(importFiled.getReadName())) { + return Result.error("第" + row + "行readName为空"); + } + if (StringUtils.isBlank(importFiled.getBitslen())) { + return Result.error("第" + row + "行bitslen为空"); + } + if(StringUtils.isBlank(importFiled.getPhyType())) { + importFiled.setPhyType("hex"); + } + if (StringUtils.isNotBlank(importFiled.getPhyType()) && !Arrays.asList(phyTypeArr).contains(importFiled.getPhyType())) { + return Result.error("第" + row + "行phyType错误"); + } + if (StringUtils.isNotBlank(importFiled.getEndian()) && ProtocolEnum.getByValue(importFiled.getEndian()) == null) { + return Result.error("第" + row + "行endian错误"); + } + if(StringUtils.isBlank(importFiled.getType())) { + importFiled.setType(ProtocolEnum.VAR.getValue()); + } + if (ProtocolEnum.getByValue(importFiled.getType()) == null) { + return Result.error("第" + row + "行type错误"); + } + if(StringUtils.isBlank(importFiled.getEndian())) { + importFiled.setEndian(ProtocolEnum.ENDIAN_BIG.getValue()); + } + Filed filed = new Filed(); + BeanUtils.copyProperties(importFiled, filed); + fileds.add(filed); + } + protocolXml.setFileds(fileds); + + Protocol protocol = new Protocol(); + protocol.setProtocolId(UUIDUtils.getUUID()); + protocol.setProtocolName(basicInfo.getReadName()); + protocol.setProtocolContent(XmlToObjectConverter.convertObjectToXml(protocolXml)); + protocol.setProtocolType(1); + protocol.setCreateTime(DateUtils.dateToString()); + protocol.setUpdateTime(DateUtils.dateToString()); + + baseMapper.insert(protocol); + + return Result.success(); + } catch (Exception e) { + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SchemeServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SchemeServiceImpl.java new file mode 100644 index 0000000..68b9382 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SchemeServiceImpl.java @@ -0,0 +1,83 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.cbsd.universaltestsoftware_client.entity.Scheme; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.InstructMapper; +import com.cbsd.universaltestsoftware_client.mapper.InvocationMapper; +import com.cbsd.universaltestsoftware_client.mapper.SchemeMapper; +import com.cbsd.universaltestsoftware_client.service.InvocationService; +import com.cbsd.universaltestsoftware_client.service.SchemeService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 方案 服务实现类 + *

+ * + * @author admin + * @since 2025-09-01 + */ +@Service +public class SchemeServiceImpl extends ServiceImpl implements SchemeService { + + @Resource + private InvocationMapper invocationMapper; + + @Resource + private InstructMapper instructMapper; + + @Override + public Result> pageList(BasePage basePage) { + BasePage serveBasePage = baseMapper.selectPage(basePage, new LambdaQueryWrapper() + .like(StringUtils.isNotBlank(basePage.getKeyword()), Scheme::getSchemeName, basePage.getKeyword()) + .orderByAsc(Scheme::getCreateTime) + ); + return Result.success(serveBasePage); + + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Result deleteScheme(String schemeId) { + if (StringUtils.isBlank(schemeId)){ + return Result.error("方案id不能为空"); + } + Scheme scheme = getById(schemeId); + if (scheme == null){ + return Result.error("方案不存在"); + } + try { + //查询方案下的用例 + List invocationList = invocationMapper.selectList(new LambdaQueryWrapper() + .eq(Invocation::getSchemeId, schemeId)); + if (invocationList != null && !invocationList.isEmpty()){ + List invocationIds = invocationList.stream().map(Invocation::getInvocationId).collect(Collectors.toList()); + //删除方案下的用例指令 + instructMapper.delete(new LambdaQueryWrapper() + .in(Instruct::getInvocationId, invocationIds)); + //删除方案下的用例 + invocationMapper.delete(new LambdaQueryWrapper() + .eq(Invocation::getSchemeId, schemeId)); + } + //删除方案 + baseMapper.deleteById(schemeId); + + }catch (Exception e){ + throw new BaseException("删除方案失败"); + } + return Result.success(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SensorEquipmentServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SensorEquipmentServiceImpl.java new file mode 100644 index 0000000..603b89e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SensorEquipmentServiceImpl.java @@ -0,0 +1,98 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.dto.SensorEquipmentDto; +import com.cbsd.universaltestsoftware_client.dto.SensorEquipmentPageDto; +import com.cbsd.universaltestsoftware_client.entity.SensorEquipment; +import com.cbsd.universaltestsoftware_client.mapper.SensorEquipmentMapper; +import com.cbsd.universaltestsoftware_client.service.SensorEquipmentService; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.UUIDUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Slf4j +public class SensorEquipmentServiceImpl extends ServiceImpl implements SensorEquipmentService { + + @Override + public Result> pageList(SensorEquipmentPageDto dto) { + Page page = new Page<>(dto.getCurrent(), dto.getSize()); + baseMapper.selectPage(page, new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(dto.getChannelId()), SensorEquipment::getChannelId, dto.getChannelId()) + .like(StringUtils.isNotBlank(dto.getEquipmentName()), SensorEquipment::getEquipmentName, dto.getEquipmentName()) + .orderByAsc(SensorEquipment::getCreateTime)); + return Result.success(page); + } + + @Override + public Result> getAllList(String channelId) { + List list = baseMapper.selectList(new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(channelId), SensorEquipment::getChannelId, channelId) + .orderByAsc(SensorEquipment::getCreateTime)); + return Result.success(list); + } + + @Override + public Result addOrUpdate(SensorEquipmentDto dto) { + if (StringUtils.isBlank(dto.getEquipmentName())){ + return Result.error("设备名称不能为空"); + } + if (StringUtils.isBlank(dto.getChannelId())){ + return Result.error("设备通道id不能为空"); + } + if (StringUtils.isBlank(dto.getChannelAddress())){ + return Result.error("设备通道地址不能为空"); + } + if (StringUtils.isNotBlank(dto.getEquipmentId())){ + SensorEquipment sensorEquipment = baseMapper.selectById(dto.getEquipmentId()); + if (sensorEquipment == null){ + return Result.error("设备不存在"); + } + sensorEquipment.setEquipmentName(dto.getEquipmentName()); + sensorEquipment.setEquipmentDesc(dto.getEquipmentDesc()); + sensorEquipment.setEquipmentParam(dto.getEquipmentParam()); + sensorEquipment.setChannelId(dto.getChannelId()); + sensorEquipment.setChannelAddress(dto.getChannelAddress()); + try { + baseMapper.updateById(sensorEquipment); + }catch (Exception e){ + e.printStackTrace(); + return Result.error("修改失败"); + } + } else { + SensorEquipment sensorEquipment = new SensorEquipment(); + sensorEquipment.setEquipmentId(UUIDUtils.getUUID()); + sensorEquipment.setEquipmentName(dto.getEquipmentName()); + sensorEquipment.setEquipmentDesc(dto.getEquipmentDesc()); + sensorEquipment.setEquipmentParam(dto.getEquipmentParam()); + sensorEquipment.setChannelId(dto.getChannelId()); + sensorEquipment.setChannelAddress(dto.getChannelAddress()); + sensorEquipment.setCreateTime(DateUtils.dateToString()); + try { + baseMapper.insert(sensorEquipment); + }catch (Exception e){ + e.printStackTrace(); + return Result.error("修改失败"); + } + } + return Result.success(); + } + + @Override + public Result delete(String equipmentId) { + try { + baseMapper.deleteById(equipmentId); + }catch (Exception e){ + e.printStackTrace(); + return Result.error("删除失败"); + } + return Result.success(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ServeServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ServeServiceImpl.java new file mode 100644 index 0000000..b8d7226 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/ServeServiceImpl.java @@ -0,0 +1,127 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cbsd.universaltestsoftware_client.config.BasePage; +import com.cbsd.universaltestsoftware_client.dto.PageServerInfoDto; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.ServerMapper; +//import com.cbsd.universaltestsoftware_client.netty.NettyClient; +import com.cbsd.universaltestsoftware_client.nettyclient.connection.ConnectionManager; +import com.cbsd.universaltestsoftware_client.service.ServeService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

+ * 服务端 服务实现类 + *

+ * + * @author admin + * @since 2025-08-26 + */ +@Service +public class ServeServiceImpl extends ServiceImpl implements ServeService { + private final ConnectionManager connectionManager; + + @Autowired + public ServeServiceImpl(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + @Override + public Result> pageList(PageServerInfoDto basePage) { + BasePage serveBasePage = baseMapper.selectPage(basePage, new LambdaQueryWrapper().eq(ServerInfo::getIsDelete, "0") + .like(StringUtils.isNotBlank(basePage.getKeyword()), ServerInfo::getServerName, basePage.getKeyword()) + .eq(basePage.getConnectStatus()!=null, ServerInfo::getConnectStatus, basePage.getConnectStatus()) + .orderByAsc(ServerInfo::getCreateTime) + ); + + List records = serveBasePage.getRecords(); + //获取状态 + Map serverConnectionStatus = connectionManager.getServerConnectionStatus(); + + for (ServerInfo record : records) { + if (CollectionUtils.isNotEmpty(serverConnectionStatus)){ + Boolean b = serverConnectionStatus.get(record.getServerId()); + if (b != null){ + record.setStatus(b ? 1 : 0); + } + } + + } + return Result.success(serveBasePage); + } + + @Override + public Result deleteById(String serverId) { +// UpdateWrapper serveUpdateWrapper = new UpdateWrapper<>(); +// serveUpdateWrapper.eq("server_id",serverId).set("is_delete",serverId) +// .set("update_time", DateUtils.dateToString()) +// ; +// baseMapper.update(null,serveUpdateWrapper); + baseMapper.deleteById(serverId); + connectionManager.removeServer(serverId); + + return Result.success(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Result connectOrDisconnect(String serverId) { + //查询服务端信息 + ServerInfo serverInfo = baseMapper.selectById(serverId); + if (serverInfo != null){ + serverInfo.setConnectStatus(1); + serverInfo.setUpdateTime(DateUtils.dateToString()); + baseMapper.updateById(serverInfo); + try { + String ipAddress = serverInfo.getIpAddress(); + Integer httpPort = serverInfo.getHttpPort(); + String url = "http://"+ipAddress+":"+httpPort+"/universaltestsoftware_server_api/outer/client/getNettyTcpPort"; + System.out.println(url); + String result = HttpUtil.post(url, new HashMap<>()); + if (StringUtils.isNotBlank(result)){ + Result bean = JSONUtil.toBean(result, Result.class); + if (bean.getCode() != 1){ + return Result.error("连接失败"); + } + serverInfo.setTcpPort(Integer.parseInt(bean.getData().toString())); + } + baseMapper.updateById(serverInfo); + + connectionManager.addAndConnectServer(serverId, serverInfo); + }catch (Exception e){ + e.printStackTrace(); + throw new BaseException("连接失败"); + } + } + + return Result.success(); + } + + @Override + public Result disconnectServer(String serverId) { + connectionManager.removeServer(serverId); + UpdateWrapper serveUpdateWrapper = new UpdateWrapper<>(); + serveUpdateWrapper.eq("server_id",serverId).set("connect_status",0) + .set("update_time", DateUtils.dateToString()) + ; + baseMapper.update(null,serveUpdateWrapper); + return Result.success(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SystemConfigServiceImpl.java b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SystemConfigServiceImpl.java new file mode 100644 index 0000000..0f185eb --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/service/impl/SystemConfigServiceImpl.java @@ -0,0 +1,77 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cbsd.universaltestsoftware_client.entity.SystemConfig; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.mapper.SystemConfigMapper; +import com.cbsd.universaltestsoftware_client.service.SystemConfigService; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author SanJie + * @description 针对表【system_config(系统配置(仅类型和内容))】的数据库操作Service实现 + * @createDate 2025-09-19 11:18:42 + */ +@Service +public class SystemConfigServiceImpl extends ServiceImpl + implements SystemConfigService { + + @Override + public Result> getShowColumnList() { + SystemConfig config = baseMapper.selectById(1); + if (config != null) { + if (StringUtils.isNotBlank(config.getContent())) { + return Result.success(Arrays.stream(config.getContent().split(",")).collect(Collectors.toList())); + } else { + return Result.success(new ArrayList<>()); + } + } + return Result.success(); + } + + @Override + public Result setShowColumnList(List columnList) { + SystemConfig config = baseMapper.selectById(1); + if (config == null) { + config = new SystemConfig(); + config.setType(1); + } + config.setContent(String.join(",", columnList)); + saveOrUpdate(config); + return Result.success(); + } + + @Override + public void setSystemConfig(int type, String value) { + try { + SystemConfig config = baseMapper.selectById(type); + if (config == null) { + config = new SystemConfig(); + config.setType(type); + baseMapper.insert(config); + } else { + config.setContent(value); + baseMapper.updateById(config); + } + } catch (Exception e) { + throw new BaseException(e.getMessage()); + } + } + + @Override + public String getSystemConfig(int type) { + SystemConfig config = baseMapper.selectById(type); + return config != null ? config.getContent() : ""; + } +} + + + + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/timing/ChannelDataTiming.java b/src/main/java/com/cbsd/universaltestsoftware_client/timing/ChannelDataTiming.java new file mode 100644 index 0000000..95778da --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/timing/ChannelDataTiming.java @@ -0,0 +1,29 @@ +package com.cbsd.universaltestsoftware_client.timing; + +import com.cbsd.universaltestsoftware_client.service.ChannelDataService; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +@EnableScheduling +public class ChannelDataTiming { + + @Resource + private ChannelDataService channelDataService; + + /** + * 晚上12点定时清理数据 + */ + @Scheduled(cron = "0 0 0 * * ?") +// @Scheduled(cron = "0 0/1 * * * ?") + public void clearChannelData() { + channelDataService.clearChannelData(); + } + + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/CalcUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/CalcUtils.java new file mode 100644 index 0000000..f23d794 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/CalcUtils.java @@ -0,0 +1,122 @@ +package com.cbsd.universaltestsoftware_client.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.zip.Adler32; +import java.util.zip.CRC32; + +public class CalcUtils { + + // 1. 奇偶校验(返回偶校验位) + public static byte parityCheck(byte[] data) { + int count = 0; + for (byte b : data) { + // 统计每个字节中1的个数 + count += Integer.bitCount(b & 0xFF); + } + // 偶校验:如果1的总数是偶数,校验位为0,否则为1 + return (byte) (count % 2 == 0 ? 0 : 1); + } + + // 2. 简单校验和(8位) + public static int simpleChecksum(byte[] data) { + if (data == null || data.length == 0) { + return 0; + } + + int sum = 0; + for (byte b : data) { + sum += b & 0xFF; // 转换为无符号整数 + } + + // 取低16位 + return sum & 0xFFFF; + } + + + // 3. CRC16校验 (Modbus标准) + public static int crc16Checksum(byte[] data) { + int crc = 0xFFFF; // 初始值 + int polynomial = 0xA001; // 多项式反转 (0x8005) + + for (byte b : data) { + crc ^= (b & 0xFF); + for (int i = 0; i < 8; i++) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= polynomial; + } else { + crc >>= 1; + } + } + } + return crc; + } + + // 4. CRC32校验 + public static long crc32Checksum(byte[] data) { + CRC32 crc32 = new CRC32(); + crc32.update(data); + return crc32.getValue(); + } + + // 6. MD5校验 + public static String md5Checksum(byte[] data) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(data); + + // 转换为十六进制字符串 + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xFF)); + } + return sb.toString(); + } + + // 7. SHA-256校验 + public static String sha256Checksum(byte[] data) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(data); + + // 转换为十六进制字符串 + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xFF)); + } + return sb.toString(); + } + + /** + * 8. 单字节异或校验 + */ + public static String byteXOR(String hexData) { + // 将组合数据按字节进行异或运算 + int xorResult = 0; + for (int i = 0; i < hexData.length(); i += 2) { // 每次处理两个字符(一个字节) + String byteStr = hexData.substring(i, i + 2); + int byteValue = Integer.parseInt(byteStr, 16); // 将十六进制字符串转换为整数 + xorResult ^= byteValue; // 与当前字节进行异或运算 + } + // 将异或结果扩展为4位十六进制字符串(后面补两个0) + return String.format("%02X", xorResult); + } + + /** + * 根据指定字节数截取/补齐十六进制结果 + * 超长取低位(右截取),不足前面补0 + */ + public static String fitToByteCount(String hexResult, Integer byteCount) { + if (byteCount == null || byteCount <= 0) { + return hexResult; + } + int targetLen = byteCount * 2; + if (hexResult.length() > targetLen) { + // 超长,取低位(取右边 targetLen 个字符) + return hexResult.substring(hexResult.length() - targetLen); + } else if (hexResult.length() < targetLen) { + // 不足,前面补0 + return String.format("%" + targetLen + "s", hexResult).replace(' ', '0'); + } + return hexResult; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/ConverUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/ConverUtils.java new file mode 100644 index 0000000..1741e8d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/ConverUtils.java @@ -0,0 +1,79 @@ +package com.cbsd.universaltestsoftware_client.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +public class ConverUtils { + + /** + * 将 key=value&key=value 格式的字符串转换为 Map + * @param str 待转换的字符串 + * @return 转换后的 Map 集合 + */ + public static Map convertStringToMap(String str) { + Map resultMap = new HashMap<>(); + + // 空值校验 + if (str == null || str.trim().isEmpty()) { + return resultMap; + } + + // 按 & 分割成单个键值对 + String[] keyValuePairs = str.split("&"); + + // 遍历每个键值对 + for (String pair : keyValuePairs) { + // 按 = 分割键和值(限制分割次数为1,避免值中包含=的情况) + String[] keyValue = pair.split("=", 2); + + // 校验分割结果是否合法 + if (keyValue.length == 2) { + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + resultMap.put(key, value); + } else if (keyValue.length == 1) { + // 处理只有key没有value的情况(如:param=&name=test) + String key = keyValue[0].trim(); + resultMap.put(key, ""); + } + } + + return resultMap; + } + + /** + * 将 Map 还原为 key=value&key=value 格式的字符串 + * @param map 待转换的Map + * @return 还原后的字符串 + */ + public static String convertMapToString(Map map) { + // 空Map直接返回空字符串 + if (map == null || map.isEmpty()) { + return ""; + } + + // 使用StringJoiner简化拼接,分隔符为& + StringJoiner joiner = new StringJoiner("&"); + + // 遍历Map中的每个键值对 + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + // 处理空键(避免出现=value的情况) + if (key == null || key.trim().isEmpty()) { + continue; + } + // 处理空值(保证key=的格式) + if (value == null) { + value = ""; + } + + // 拼接单个key=value并加入joiner + joiner.add(key.trim() + "=" + value.trim()); + } + + return joiner.toString(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/DataUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/DataUtils.java new file mode 100644 index 0000000..e729363 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/DataUtils.java @@ -0,0 +1,274 @@ +package com.cbsd.universaltestsoftware_client.util; + +import java.nio.ByteBuffer; +import java.util.List; + +public class DataUtils { + + public static String byteToHex(byte data) { + int h = (data >>> 4) & 0xF; + int l = data & 0xF; + char ch = (char) ((h < 10) ? ('0' + h) : ('A' + h - 10)); + char cl = (char) ((l < 10) ? ('0' + l) : ('A' + l - 10)); + return String.valueOf(ch) + cl; + } + + public static String byteToHex(byte[] data) { + StringBuilder sb = new StringBuilder(); + for (byte b : data) { + sb.append(byteToHex(b)); + } + return sb.toString(); + } + + /** + * 将字节数组转换为int(大端序,高位在前) + * + * @param bytes 输入字节数组 + * @return 转换后的int值 + */ + public static long toIntBigEndian(byte[] bytes) { + int result = 0; + int length = Math.min(bytes.length, 4); // 只取前4个字节 + + for (int i = 0; i < length; i++) { + result = (result << 8) | (bytes[i] & 0xFF); + } + return result; + } + + /** + * 将字节数组转换为int(小端序,低位在前) + * + * @param bytes 输入字节数组 + * @return 转换后的int值 + */ + public static long toIntLittleEndian(byte[] bytes) { + int result = 0; + int length = Math.min(bytes.length, 4); // 只取前4个字节 + + for (int i = 0; i < length; i++) { + result |= (bytes[i] & 0xFF) << (8 * i); + } + return result; + } + + /** + * byte列表转字节数组 + * + * @param byteList + * @return + */ + public static byte[] byteListToBytes(List byteList) { + if (byteList == null) { + return null; + } + // 创建与列表大小相同的 byte 数组 + byte[] byteArray = new byte[byteList.size()]; + // 逐个将 Byte 对象转换为 byte 基本类型并填入数组 + for (int i = 0; i < byteList.size(); i++) { + // 使用 Byte 的 byteValue () 方法获取基本类型值 + byteArray[i] = byteList.get(i); + } + return byteArray; + } + + /** + * 将double数组转换为byte数组(不丢失精度) + * + * @param doubles 输入的double数组 + * @return 转换后的byte数组 + */ + public static byte[] doublesToBytes(double[] doubles) { + // 每个double占8个字节,计算所需的总字节数 + int byteLength = doubles.length * 8; + // 创建指定容量的ByteBuffer + ByteBuffer buffer = ByteBuffer.allocate(byteLength); + + // 将所有double值放入缓冲区 + for (double d : doubles) { + buffer.putDouble(d); + } + + // 返回缓冲区中的字节数组 + return buffer.array(); + } + + /** + * 将byte数组转换回double数组(还原原始数据) + * + * @param bytes 输入的byte数组 + * @return 转换后的double数组 + * @throws IllegalArgumentException 如果字节数组长度不是8的倍数 + */ + public static double[] bytesToDoubles(byte[] bytes) { + if (bytes.length % 8 != 0) { + throw new IllegalArgumentException("字节数组长度必须是8的倍数"); + } + + // 计算可以转换的double数量 + int doubleLength = bytes.length / 8; + // 创建ByteBuffer并放入字节数组 + ByteBuffer buffer = ByteBuffer.wrap(bytes); + double[] doubles = new double[doubleLength]; + + // 从缓冲区中读取所有double值 + for (int i = 0; i < doubleLength; i++) { + doubles[i] = buffer.getDouble(); + } + + return doubles; + } + + /** + * 将byte转换为8位二进制字符串 + * + * @param b 要转换的byte值 + * @return 8位二进制字符串 + */ + public static String byteToBinary(byte b) { + // 将byte转换为无符号整数,避免符号位扩展 + int unsignedByte = b & 0xFF; + // 转换为二进制字符串 + String binary = Integer.toBinaryString(unsignedByte); + // 确保结果是8位,不足的在前面补0 + while (binary.length() < 8) { + binary = "0" + binary; + } + return binary; + } + + /** + * 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数 + * + * @param num + * @return + */ + public static int isOdd(int num) { + return num & 1; + } + + /** + * 转hex字符串转字节数组 + * + * @param inHex + * @return + */ + public static byte[] HexToByteArr(String inHex) { + byte[] result; + int hexlen = inHex.length(); + if (isOdd(hexlen) == 1) { + hexlen++; + result = new byte[(hexlen / 2)]; + inHex = "0" + inHex; + } else { + result = new byte[(hexlen / 2)]; + } + int j = 0; + for (int i = 0; i < hexlen; i += 2) { + result[j] = HexToByte(inHex.substring(i, i + 2)); + j++; + } + return result; + } + + /** + * Hex字符串转byte + * + * @param inHex + * @return + */ + public static byte HexToByte(String inHex) { + return (byte) Integer.parseInt(inHex, 16); + } + + /** + * 将byte转换为byte类型的bit数组(8位,保留所有0) + * + * @param b 待转换的byte + * @return 长度为8的byte数组,元素为0或1(索引0对应最高位) + */ + public static byte[] byteToByteBitArray(byte b) { + byte[] bits = new byte[8]; + // 遍历8个比特位,从最高位(第7位)到最低位(第0位) + for (int i = 7; i >= 0; i--) { + // 关键修正:将int结果强转为byte,避免隐式转换警告 + bits[7 - i] = (byte) ((b >> i) & 1); + } + return bits; + } + + // 优化版:无需生成单个byte的中间bit数组,直接计算填充 + public static byte[] byteArrayToBitArray(byte[] byteArray) { + if (byteArray == null || byteArray.length == 0) { + return new byte[0]; + } + byte[] resultBits = new byte[byteArray.length * 8]; + int index = 0; + for (byte b : byteArray) { + // 直接遍历8位,计算后填充到结果数组,省去中间数组 + for (int i = 7; i >= 0; i--) { + resultBits[index++] = (byte) ((b >> i) & 1); + } + } + return resultBits; + } + + /** + * 将bit数组转换为byte数组, 填充0 + * + * @param bitArray 输入的bit数组, 如果长度不足8的倍数,则在前部填充0 + * @return 转换后的byte数组 + */ + public static byte[] bitArrayToByteArray(byte[] bitArray) { + if (bitArray == null || bitArray.length == 0) { + return new byte[0]; + } + if(bitArray.length % 8 != 0) { + // 创建一个与bitArray长度相同的byte数组,用于填充0 + byte[] paddedBitArray = new byte[bitArray.length + (8 - bitArray.length % 8)]; + // 将bitArray复制到paddedBitArray中,并填充0 + System.arraycopy(bitArray, 0, paddedBitArray, paddedBitArray.length - bitArray.length, bitArray.length); + bitArray = paddedBitArray; + } + int byteLength = bitArray.length / 8; + byte[] result = new byte[byteLength]; + for (int i = 0; i < byteLength; i++) { + result[i] = (byte) ((bitArray[i * 8] << 7) | + (bitArray[i * 8 + 1] << 6) | + (bitArray[i * 8 + 2] << 5) | + (bitArray[i * 8 + 3] << 4) | + (bitArray[i * 8 + 4] << 3) | + (bitArray[i * 8 + 5] << 2) | + (bitArray[i * 8 + 6] << 1) | + (bitArray[i * 8 + 7])); + } + return result; + } + + /** + * 将bit数组转化为二进制字符串,不足则补0 + * @param bits 比特数组 + * @return 二进制字符串 + */ + public static String bitsToBinaryString(byte[] bits) { + StringBuilder sb = new StringBuilder(); + for (byte bit : bits) { + sb.append(bit); + if (sb.length() % 8 == 0) { + sb.append(" "); + } + } + //不足八位,前面补0 +// while (sb.length() % 8 != 0) { +// sb.insert(0, "0"); +// } + return sb.toString(); + } + + public static void main(String[] args) { + byte[] bytes = new byte[]{1,0,1,0}; + String result = bitsToBinaryString(bytes); + System.out.println(result); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/DateUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/DateUtils.java new file mode 100644 index 0000000..8fae03e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/DateUtils.java @@ -0,0 +1,51 @@ +package com.cbsd.universaltestsoftware_client.util; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class DateUtils { + + public static String dateToString() { + return dateToString(null, null); + } + + public static String dateToString(String pattern) { + return dateToString(null, pattern); + } + + public static String dateToString(Date date, String pattern) { + if (date == null) { + date = new Date(); + } + if (StringUtils.isBlank(pattern)) { + pattern = "yyyy-MM-dd HH:mm:ss"; + } + SimpleDateFormat normalFormat = new SimpleDateFormat(pattern, Locale.CHINA); + return normalFormat.format(date); + } + + public static String timestampToDateString(long timestamp) { + return timestampToDateString(timestamp, null); + } + + public static String timestampToDateString(long timestamp, String pattern) { + Date date = new Date(timestamp); + return dateToString(date, pattern); + } + + public static long stringToTimestamp(String dateString) { + if (StringUtils.isBlank(dateString)) { + return 0; + } + SimpleDateFormat normalFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA); + try { + Date date = normalFormat.parse(dateString); + return date.getTime(); + } catch (Exception e) { + return 0; + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/DynamicClassLoader.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/DynamicClassLoader.java new file mode 100644 index 0000000..290f554 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/DynamicClassLoader.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.util; + +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; + +public class DynamicClassLoader extends URLClassLoader { + public DynamicClassLoader(String rootPath) throws Exception { + super(new URL[]{ Paths.get(rootPath).toUri().toURL() }, + Thread.currentThread().getContextClassLoader()); + } +} + diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/FileUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/FileUtils.java new file mode 100644 index 0000000..49b1677 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/FileUtils.java @@ -0,0 +1,37 @@ +package com.cbsd.universaltestsoftware_client.util; + +import org.springframework.boot.system.ApplicationHome; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class FileUtils { + + /** + * 获取目录下的所有文件 + * + * @param path 目录地址 + */ + public static List getDirectoryFiles(String path) { + List fileList = new ArrayList<>(); + File dic = new File(path); + if (dic.exists() && dic.isDirectory() && dic.listFiles() != null) { + for (File file : Objects.requireNonNull(dic.listFiles())) { + if (file.isFile()) { + fileList.add(file); + } + } + } + return fileList; + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/MediaTypeUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/MediaTypeUtils.java new file mode 100644 index 0000000..bb2a33f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/MediaTypeUtils.java @@ -0,0 +1,56 @@ +package com.cbsd.universaltestsoftware_client.util; + + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; + +public class MediaTypeUtils { + + /** + * 根据文件后缀获取MIME 类型,默认:text/html + * @param suffix 文件后缀,包含点(.) + * @return MIME 类型 + */ + public static String getMediaType(String suffix) { + + if (StringUtils.isNotBlank(suffix)){ + switch (suffix.toLowerCase()) { + case ".jpg": + case ".jpeg": + return "image/jpeg"; + case ".png": + return "image/png"; + case ".gif": + return "image/gif"; + case ".aac": + return "audio/aac"; + case ".mp3": + return "audio/mpeg"; + case ".mp4": + return "video/mp4"; + case ".mpeg": + return "video/mpeg"; + case ".txt": + return "text/plain"; + case ".json": + return "application/json"; + case ".doc": + return "application/msword"; + case ".docx": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case ".xls": + return "application/vnd.ms-excel"; + case ".xlsx": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case ".ppt": + return "application/vnd.ms-powerpoint"; + case ".pptx": + return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + case ".pdf": + return "application/pdf"; + case ".apk": + return "application/vnd.android.package-archive"; + } + } + return "text/html"; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/ModbusCRC16Utils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/ModbusCRC16Utils.java new file mode 100644 index 0000000..baa59d6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/ModbusCRC16Utils.java @@ -0,0 +1,89 @@ +package com.cbsd.universaltestsoftware_client.util; + + +public class ModbusCRC16Utils { + + /** + * 计算Modbus CRC16校验值 + * + * @param data 待校验的数据 + * @return CRC16校验值,2字节数组 + */ + public static byte[] calculateCRC(byte[] data) { + int crc = 0xFFFF; + for (byte b : data) { + crc ^= (b & 0xFF); + for (int i = 0; i < 8; i++) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + // 将CRC转换为字节数组(低字节在前,高字节在后) + byte[] result = new byte[2]; + result[0] = (byte) (crc & 0xFF); + result[1] = (byte) ((crc >> 8) & 0xFF); + + return result; + } + + /** + * 计算Modbus CRC16校验值并以十六进制字符串形式返回 + * + * @param data 待校验的数据 + * @return CRC16校验值的十六进制字符串 + */ + public static String calculateCRCToHexString(byte[] data) { + byte[] crcBytes = calculateCRC(data); + return String.format("%02X%02X", crcBytes[0] & 0xFF, crcBytes[1] & 0xFF); + } + + /** + * 获取源数据和验证码的组合byte数组 + * + * @param strings 可变长度的十六进制字符串 + */ +// public static byte[] getData(String... strings) { +// byte[] data = DataUtils.getData(strings); +// return getData(data); +// } + + /** + * 获取源数据和验证码的组合byte数组 + * + * @param bytes 字节数组 + */ + private static byte[] getData(byte[] bytes) { + byte[] bb = calculateCRC(bytes); + byte[] cc = new byte[bytes.length + bb.length]; + System.arraycopy(bytes, 0, cc, 0, bytes.length); + System.arraycopy(bb, 0, cc, bytes.length, bb.length); + return cc; + } + + /** + * 验证数据的CRC16校验值 + * + * @param data 包含CRC校验值的数据(CRC在数据末尾,2字节) + * @return 校验结果,true表示校验通过,false表示校验失败 + */ + public static boolean verifyCRC(byte[] data) { + if (data == null || data.length < 2) { + return false; + } + + // 提取数据部分(不包含CRC) + byte[] dataWithoutCRC = new byte[data.length - 2]; + System.arraycopy(data, 0, dataWithoutCRC, 0, data.length - 2); + + // 计算CRC + byte[] calculatedCRC = calculateCRC(dataWithoutCRC); + + // 比较计算得到的CRC与数据中包含的CRC + return calculatedCRC[0] == data[data.length - 2] && + calculatedCRC[1] == data[data.length - 1]; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/ProtocolToolUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/ProtocolToolUtils.java new file mode 100644 index 0000000..761a96d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/ProtocolToolUtils.java @@ -0,0 +1,37 @@ +package com.cbsd.universaltestsoftware_client.util; + +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.cbsd.universaltestsoftware_client.parser.model.Filed; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import org.springframework.stereotype.Component; + +import javax.xml.bind.JAXBException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + + +public class ProtocolToolUtils { + + + + /** + * 解析协议参数 + * @param protocolContent + * @return + */ + public static List getProtocolParameter(String protocolContent) throws JAXBException { + List parameter = new ArrayList<>(); + ProtocolXml protocolXml = XmlToObjectConverter.convertXmlToObject(protocolContent, ProtocolXml.class); + if (protocolXml != null) { + List fileds = protocolXml.getFileds(); + fileds.stream().sorted(Comparator.comparingInt(Filed::getSnInt)); + if (CollectionUtils.isNotEmpty(fileds)) { + for (Filed filed : fileds) { + parameter.add(filed.getReadName()); + } + } + } + return parameter; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/Result.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/Result.java new file mode 100644 index 0000000..d92db8e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/Result.java @@ -0,0 +1,105 @@ +package com.cbsd.universaltestsoftware_client.util; + +import com.alibaba.fastjson.JSON; +import com.cbsd.universaltestsoftware_client.exception.BaseException; +import com.cbsd.universaltestsoftware_client.exception.CustomizeException; + + +/** + * 统一API响应结果封装 + * + * @author LH @date 2018年11月14日 + */ +public class Result { + /** + * 编码 + */ + private Integer code; + /** + * 描述 + */ + private String msg; + + /** + * 业务数据 + */ + + + + private T data; + + public Integer getCode() { + return code; + } + + public Result setCode(Integer code) { + this.code = code; + return this; + } + + public String getMsg() { + return msg; + } + + public Result setMsg(String msg) { + this.msg = msg; + return this; + } + + public T getData() { + return data; + } + + public Result setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } + + public static Result success() { + return new Result().setCode(ResultCode.OK).setMsg("操作成功"); + } + + public static Result success(T data) { + return new Result().setCode(ResultCode.OK).setMsg("操作成功").setData(data); + } + public static Result success(T data,String msg) { + return new Result().setCode(ResultCode.OK).setMsg(msg).setData(data); + } + + public static Result error() { + return new Result().setCode(ResultCode.SYSTEM_ERROR).setMsg("系统错误"); + } + + public static Result error(String message) { + return new Result().setCode(ResultCode.SYSTEM_ERROR).setMsg(message); + } + + public static Result error(Integer code, String message) { + return new Result().setCode(code).setMsg(message); + } + public static Result error(Integer code, String message,T data) { + return new Result().setCode(code).setMsg(message).setData(data); + } + + // added by qidongbo 20211210 直接传入异常对象 + public static Result error(CustomizeException exception) { + return new Result().setCode(Integer.parseInt(exception.getCode())).setMsg(exception.getMessage()); + } + + public static Result throwError() { + return throwError(ResultCode.SYSTEM_ERROR,"系统错误"); + } + + public static Result throwError(String message) { + return throwError(ResultCode.SYSTEM_ERROR,message); + } + + public static Result throwError(Integer code,String message) { + throw new BaseException(code,message); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/ResultCode.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/ResultCode.java new file mode 100644 index 0000000..e15ebda --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/ResultCode.java @@ -0,0 +1,263 @@ +package com.cbsd.universaltestsoftware_client.util; + +/** + *

+ * 全局请求返回状态标识 + *

+ * + * @author chenlin + * @date 2019-10-29 + */ +public final class ResultCode { + + //========================全局请求返回状态标识开始======================= + /** + * 操作成功 + */ + public static final int OK = 1; + /** + * 用户未登陆 + */ + public static final int NOT_LOGIN = -1; + /** + * 不能为空 + */ + public static final int CAN_NOT_NULL = -2; + /** + * 参数类型错误 + */ + public static final int PARAMER_TYPE_ERROR = -3; + /** + * 未知错误 + */ + public static final int UNKNOW_ERROR = -4; + /** + * 系统错误 + */ + public static final int SYSTEM_ERROR = -5; +// /** +// * access_token 错误 +// */ +// public static final int ACCESS_TOKEN_ERROR = -6; + /** + * 参数超长 + */ + public static final int TOO_LANG = -7; + /** + * 暂无数据 + */ + public static final int NO_DATA = -8; + /** + * 前端不需要处理的错误 + */ + public static final int NOT_NEED_DEAL = -9; + /** + * 签名错误 + */ + public static final int SIGN_ERROR = -10; + + /** + * 枚举错误 + */ + public static final int ENUM_ERROR = -11; + + /** + *excel操作异常 + */ + public static final int EXCEL_ABNORMAL = -12; + + /** + * 商务部cookie过期 + */ + public static final int MINISTRY_COMMERCE_COOKIE = -13; + /** + * 用户权限不足 + */ + public static final int INSUFFCIENT_PERMISSIONS = -16; + + /** + * 重复提交 + */ + public static final int REPEAT_COMMIT = -17; + + /** + * 版本错误 + */ + public static final int VERTION_ERROR = -18; + + /** + * 公司信息未完善 + */ + public static final int NOT_PERFECT = -19; + /** + * 审核状态错误 + */ + public static final int STATE_ERROR = -21; + /** + * 已超限 + */ + public static final int OVERRUN = -22; + /** + * 重复数据 + */ + public static final int REPEATING_DATA = -23; + + + public static final int DATA_BASE_TIMEOUT = -24; + + public static final int DATA_BASE_ERROR = -25; + + /** + * 低于等于10 + */ + public static final int LOWER_ten=-26; + + /** + * 连续超过5次登录失败 + */ + public static final int ACOUNT_INVALID_FIVE = 100018; + /** + + /** + * 低于等于0 + */ + public static final int LOWER_zero=-27; + + //========================全局请求返回状态标识结束======================= + + //========================接口返回状态标识开始========================== + + /** + * 错误的手机号格式 + */ + public static final int NOT_TRUE_MOBILE = 100001; + /** + * 验证码错误 + */ + public static final int SMS_CODE_ERROR = 100002; + /** + * 用户不存在 + */ + public static final int USER_NOT_EXIST = 100003; + /** + * 密码错误 + */ + public static final int PASSWORD_ERROR = 100004; + /** + * appid或secret错误 + */ + public static final int TOKEN_USER_NOT_VALID = 100005; + /** + * 原始密码错误 + */ + public static final int OLD_PASSWORD_ERROR = 100006; + + /** + * 图形验证码错误 + */ + public static final int PIC_VERIFICATION_ERROR = 100008; + /** + * 错误的邮箱格式 + */ + public static final int NOT_TRUE_EMAIL = 100009; + /** + * 邮箱不存在 + */ + public static final int EMAIL_ISNOT_BIND = 100010; + /** + * 手机号已经注册 + */ + public static final int MOBILE_IS_REGIST = 100011; + /** + * 手机号未注册 + */ + public static final int MOBILE_ISNOT_REGIST = 100012; + /** + * 邮箱已经绑定 + */ + public static final int EMAIL_IS_BINDED = 100013; + /** + * 邮箱是当前用户已经绑定的邮箱 + */ + public static final int EMAIL_IS_CURRENT = 100015; + /** + * 账号被禁用 + */ + public static final int ACOUNT_INVALID = 100014; + /** + * 原密码和新密码相同 + */ + public static final int PASSWORD_SAME = 100016; + /** + * 名字重复 + */ + public static final int NAME_SAME = 100017; + /** + * 参数取值错误 + */ + public static final int PARAMETER_ERROR = 100031; + + + ///////////////上传资料错误码开始//////////////////////////// + /** + * 上传资料不是图片 + */ + public static final int IS_NOT_PIC = 110001; + /** + * 上传资料格式不对 + */ + public static final int FORMAT_IS_FALSE = 110002; + /** + * 上传头像尺寸不对不是图片 + */ + public static final int PIXEL_RATIO_EXCEPTION = 110003; + ///////////////上传资料错误码开始//////////////////////////// + + //========================接口返回状态标识结束========================== + /** + * 用户类型错误 + */ + public static final int USER_TYPE_ERROR = 100025; + /** + * 手机号未注册 + */ + public static final int NOT_PHONE = 100026; + + /** + * 测试版本 + */ + public static final int TEST_VERSION = 6666; + /** + * 过期版本 + */ + public static final int EXPIRED_VERSION = -9999; + /** + * OK版本 + */ + public static final int OK_VERSION = 1111; + /** + * 就餐失败 + */ + public static final int Not_jiuCan = 1001; + + /** + * REDIS服务器错误 + */ + public static final int REDIS_NOT = 50002; + + /** + * 托盘未绑定 + */ + public static final int PLATE_NOT_BIND = 50003; + + /** + * 租户Id未传 + */ + public static final int NOT_BIND_PLATE = 50004; + + /** + * 请求异常 + */ + public static final int REQUEST_ERROR = 50005; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/SplitUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/SplitUtils.java new file mode 100644 index 0000000..b64ecb5 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/SplitUtils.java @@ -0,0 +1,25 @@ +package com.cbsd.universaltestsoftware_client.util; + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class SplitUtils { + + public static List splitStringByLength(String str, int length) { + List result = new ArrayList<>(); + // 处理空字符串或负数长度的情况 + if (StringUtils.isBlank(str) || length <= 0) { + result.add(str); + } else { + // 按照指定长度拆分字符串 + for (int i = 0; i < str.length(); i += length) { + // 计算结束索引,避免越界 + int endIndex = Math.min(i + length, str.length()); + result.add(str.substring(i, endIndex)); + } + } + return result; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/UUIDUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/UUIDUtils.java new file mode 100644 index 0000000..49ceb33 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/UUIDUtils.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.util; + +import java.util.UUID; + +/** + *

+ * 字符串工具类 + *

+ * + * @author chenlin + * @date 2019-10-29 + */ +public class UUIDUtils { + /** + * 获取uuid字符串 + * @return + */ + public static String getUUID() { + return UUID.randomUUID().toString().replaceAll("-", "").toLowerCase(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/XmlToObjectConverter.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/XmlToObjectConverter.java new file mode 100644 index 0000000..922dbb7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/XmlToObjectConverter.java @@ -0,0 +1,40 @@ +package com.cbsd.universaltestsoftware_client.util; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; + +public class XmlToObjectConverter { + public static T convertXmlToObject(String xmlString, Class clazz) throws JAXBException { + + // 创建JAXB上下文 + JAXBContext jaxbContext = JAXBContext.newInstance(clazz); + + // 创建Unmarshaller + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + + // 使用StringReader读取XML字符串并转换为对象 + try (StringReader reader = new StringReader(xmlString)) { + return clazz.cast(unmarshaller.unmarshal(reader)); + } + } + + /** + * 实体类转XML字符串 + */ + public static String convertObjectToXml(T entity) throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(entity.getClass()); + Marshaller marshaller = jaxbContext.createMarshaller(); + + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); + + StringWriter writer = new StringWriter(); + marshaller.marshal(entity, writer); + + return writer.toString().replace("", ""); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/util/YamlUtils.java b/src/main/java/com/cbsd/universaltestsoftware_client/util/YamlUtils.java new file mode 100644 index 0000000..2cadaee --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/util/YamlUtils.java @@ -0,0 +1,86 @@ +package com.cbsd.universaltestsoftware_client.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; + +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +/** + * + * yaml转换工具 + * + * @author 00fly + * @version [版本号, 2023年4月25日] + * @see [相关类/方法] + * @since [产品/模块版本] + */ +public final class YamlUtils +{ + private static YAMLMapper yamlMapper = new YAMLMapper(); + + private static JavaPropsMapper javaPropsMapper = new JavaPropsMapper(); + + private YamlUtils() + { + super(); + } + + /** + * yaml转Json字符串 + * + * @param yamlContent + * @return + * @throws IOException + */ + public static String yamlToJson(String yamlContent) + throws IOException + { + JsonNode jsonNode = yamlMapper.readTree(yamlContent); + return jsonNode.toPrettyString(); + } + + /** + * yaml转Map + * + * @param yamlContent + * @return + * @throws IOException + */ + public static Map yamlToMap(String yamlContent) + throws IOException + { + JsonNode jsonNode = yamlMapper.readTree(yamlContent); + return javaPropsMapper.writeValueAsMap(jsonNode); + } + + /** + * yaml转properties + * + * @param yamlContent + * @return + * @throws IOException + */ + public static Properties yamlToProperties(String yamlContent) + throws IOException + { + JsonNode jsonNode = yamlMapper.readTree(yamlContent); + return javaPropsMapper.writeValueAsProperties(jsonNode); + } + + /** + * yaml转properties字符串 + * + * @param yamlContent + * @return + * @throws IOException + */ + public static String yamlToPropText(String yamlContent) + throws IOException + { + JsonNode jsonNode = yamlMapper.readTree(yamlContent); + return javaPropsMapper.writeValueAsString(jsonNode); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/BoardListVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/BoardListVo.java new file mode 100644 index 0000000..cb8180e --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/BoardListVo.java @@ -0,0 +1,34 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +@Data +public class BoardListVo { + @ApiModelProperty("板卡id") + private String boardId; + + @ApiModelProperty("板卡类型Id") + private String boardTypeId; + + @ApiModelProperty("板卡类型名称 用户看") + @TableField(exist = false) + private String boardTypeName; + + @ApiModelProperty("板卡名称") + private String boardName; + + @ApiModelProperty("板卡序号") + private String boardIndex; + + + @ApiModelProperty("所有的通道") + private List boardListVos; + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataHistoryVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataHistoryVo.java new file mode 100644 index 0000000..8df86a6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataHistoryVo.java @@ -0,0 +1,45 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class ChannelDataHistoryVo { + + @ApiModelProperty("通道历史数据id") + @TableId(value = "channel_data_history_id", type = IdType.ASSIGN_UUID) + private String channelDataHistoryId; + + @ApiModelProperty("通道id") + private String channelId; + + @ApiModelProperty("创建时间") + private String createTime; + + + @ApiModelProperty("是否删除0否1是") + private String isDelete; + + @ApiModelProperty("数据信息") + private byte[] content; + + @ApiModelProperty("数据字符串") + @TableField(exist = false) + private String contentString; + + @ApiModelProperty("时间戳") + private Long timestamp; + + /** + * 额外数据 + */ + @ApiModelProperty("额外数据") + private String extraData; + + @ApiModelProperty("通道名字") + @TableField(exist = false) + private String channelName; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataStatisticsVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataStatisticsVo.java new file mode 100644 index 0000000..481f10f --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataStatisticsVo.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class ChannelDataStatisticsVo { + + private String channelId; + + private Integer dataByteCount; + + private List protocolStatisticsList; + + @Data + public static class ProtocolStatisticsVo { + private String protocolId; + private Integer parseCount; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataVo.java new file mode 100644 index 0000000..0c092a3 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelDataVo.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import com.cbsd.universaltestsoftware_client.parser.model.FiledParseData; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ChannelDataVo { + + private String channelId; + + private List filedParseDataList; + + private String protocolContent; + + private long timestamp; + + private String dataBoardId; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelListVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelListVo.java new file mode 100644 index 0000000..396666d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ChannelListVo.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class ChannelListVo { + @ApiModelProperty("通道id") + private String channelId; + + @ApiModelProperty("通道名称") + private String channelName; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ExportDataBoardVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ExportDataBoardVo.java new file mode 100644 index 0000000..ecc9b94 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ExportDataBoardVo.java @@ -0,0 +1,31 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class ExportDataBoardVo { + + + @ApiModelProperty("通道名字") + private String channelName; + + @ApiModelProperty("协议名字") + private String protocolName; + + @ApiModelProperty("数据字段") + private String dataField; + + @ApiModelProperty("源数据") + private String sourceData; + + @ApiModelProperty("物理解析值") + private String thingValue; + + @ApiModelProperty("物理单位 ") + private String physicalUnit; + + @ApiModelProperty("时间") + private String time; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/InstructScriptVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/InstructScriptVo.java new file mode 100644 index 0000000..26ad9be --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/InstructScriptVo.java @@ -0,0 +1,16 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import lombok.Data; + +@Data +public class InstructScriptVo { + + private String instructId; + + private String scriptType; + + private String scriptFile; + + private String scriptName; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/InvocationInstructVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/InvocationInstructVo.java new file mode 100644 index 0000000..f1bc844 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/InvocationInstructVo.java @@ -0,0 +1,17 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import lombok.Data; + +import java.util.List; + +@Data +public class InvocationInstructVo { + + private String invocationId; + + private String invocationName; + + private List instructList; + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ProtocolSaveVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ProtocolSaveVo.java new file mode 100644 index 0000000..d69b9cb --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ProtocolSaveVo.java @@ -0,0 +1,11 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import com.cbsd.universaltestsoftware_client.entity.Protocol; +import com.cbsd.universaltestsoftware_client.parser.model.ProtocolXml; +import lombok.Data; + +@Data +public class ProtocolSaveVo { + private Protocol protocol; + private ProtocolXml parse; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ScriptTypeVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ScriptTypeVo.java new file mode 100644 index 0000000..85857a1 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ScriptTypeVo.java @@ -0,0 +1,15 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import lombok.Data; + +@Data +public class ScriptTypeVo { + + private String scriptType; + + private String scriptTypeName; + + private String scriptDirectoryPath; + + private String scriptTemplateContent; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ScriptVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ScriptVo.java new file mode 100644 index 0000000..ed1f5b6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ScriptVo.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import lombok.Data; + +@Data +public class ScriptVo { + + private String fileName; + + private String filePath; + + private String scriptName; + + private String scriptType; + + private String scriptComment; + + private int compileStatus; + + private String lastModifiedTime; +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/vo/ServerListVo.java b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ServerListVo.java new file mode 100644 index 0000000..8dcccb6 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/vo/ServerListVo.java @@ -0,0 +1,36 @@ +package com.cbsd.universaltestsoftware_client.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +@Data +public class ServerListVo { + + @ApiModelProperty("服务id") + private String serverId; + + @ApiModelProperty("服务端名字") + private String serverName; + + @ApiModelProperty("ip地址") + private String ipAddress; + + @ApiModelProperty("服务端tcp端口") + private Integer tcpPort; + + @ApiModelProperty("服务端http端口") + private Integer httpPort; + + @ApiModelProperty("所有板卡") + private List boardListVos; + + + + + + + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataPlayBackWebSocketConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataPlayBackWebSocketConfig.java new file mode 100644 index 0000000..f964bb0 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataPlayBackWebSocketConfig.java @@ -0,0 +1,24 @@ +package com.cbsd.universaltestsoftware_client.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +@Configuration +@EnableWebSocket +public class DataPlayBackWebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + // 注册WebSocket处理器,映射路径为/ws/chat + registry.addHandler(dataPlayBackWebSocketHandler(), "/ws/playback") + .setAllowedOrigins("*"); // 允许所有来源,生产环境应指定具体域名 + } + + @Bean + public DataPlayBackWebSocketHandler dataPlayBackWebSocketHandler() { + return new DataPlayBackWebSocketHandler(); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataPlayBackWebSocketHandler.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataPlayBackWebSocketHandler.java new file mode 100644 index 0000000..ffae606 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataPlayBackWebSocketHandler.java @@ -0,0 +1,74 @@ +package com.cbsd.universaltestsoftware_client.websocket; + +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class DataPlayBackWebSocketHandler extends TextWebSocketHandler { + + // 存储所有连接的会话,key为"IP:端口",value为WebSocketSession + public static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + // 获取客户端IP和端口 + String clientId = getClientId(session); + + // 存储会话 + sessions.put(clientId, session); + + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + // 获取发送者标识 + String payload = message.getPayload(); + System.out.println("收到客户端消息: " + payload); + String senderId = getClientId(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + // 获取客户端标识 + String clientId = getClientId(session); + + // 移除会话 + sessions.remove(clientId); + } + + /** + * 获取客户端唯一标识(IP:端口) + */ + private String getClientId(WebSocketSession session) { + InetSocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress != null) { + return remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort(); + } + return "unknown:" + session.getId(); + } + + /** + * 广播消息给所有连接的客户端 + */ + public void broadcast(String message) { + Set clientIds = sessions.keySet(); + + for (String clientId : clientIds) { + WebSocketSession session = sessions.get(clientId); + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(message)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebNettyWebSocket.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebNettyWebSocket.java new file mode 100644 index 0000000..7b0f7e9 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebNettyWebSocket.java @@ -0,0 +1,54 @@ +package com.cbsd.universaltestsoftware_client.websocket; + +import io.netty.handler.codec.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.yeauty.annotation.*; +import org.yeauty.pojo.Session; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +//@Component +//@EnableWebSocket +//@ServerEndpoint(port = "8083",path = "/universaltestsoftware_client_api/nettyWs/data") +public class DataWebNettyWebSocket { + + private static final Map sessionMap = new ConcurrentHashMap<>(); + + @BeforeHandshake + public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){ + + } + + @OnOpen + public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){ + String clientId = session.remoteAddress().toString(); + sessionMap.put(clientId, session); + } + + @OnClose + public void onClose(Session session) throws IOException { + String clientId = session.remoteAddress().toString(); + sessionMap.remove(clientId); + } + + @OnError + public void onError(Session session, Throwable throwable) { + throwable.printStackTrace(); + } + + @OnMessage + public void onMessage(Session session, String message) { + + } + + public void sendMessage(String message) { + for (Session session : sessionMap.values()) { + if (session != null && session.isOpen()){ + session.sendText(message); + } + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebSocketConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebSocketConfig.java new file mode 100644 index 0000000..7466dc0 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebSocketConfig.java @@ -0,0 +1,24 @@ +package com.cbsd.universaltestsoftware_client.websocket; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; + +@Configuration +@EnableWebSocket +public class DataWebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + // 注册WebSocket处理器,映射路径为/ws/chat + registry.addHandler(dataWebSocketHandler(), "/ws/data") + .setAllowedOrigins("*"); // 允许所有来源,生产环境应指定具体域名 + } + + @Bean + public DataWebSocketHandler dataWebSocketHandler() { + return new DataWebSocketHandler(); + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebSocketHandler.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebSocketHandler.java new file mode 100644 index 0000000..3eefbbc --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/DataWebSocketHandler.java @@ -0,0 +1,71 @@ +package com.cbsd.universaltestsoftware_client.websocket; + +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DataWebSocketHandler extends TextWebSocketHandler { + + // 存储所有连接的会话,key为"IP:端口",value为WebSocketSession + private static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + // 获取客户端IP和端口 + String clientId = getClientId(session); + + // 存储会话 + sessions.put(clientId, session); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + // 获取发送者标识 + String payload = message.getPayload(); + String senderId = getClientId(session); + + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + // 获取客户端标识 + String clientId = getClientId(session); + + // 移除会话 + sessions.remove(clientId); + } + + /** + * 获取客户端唯一标识(IP:端口) + */ + private String getClientId(WebSocketSession session) { + InetSocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress != null) { + return remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort(); + } + return "unknown:" + session.getId(); + } + + /** + * 广播消息给所有连接的客户端 + */ + public void broadcast(String message) { + synchronized (sessions) { + for (WebSocketSession session : sessions.values()) { + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(message)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/NoticeWebSocketConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/NoticeWebSocketConfig.java new file mode 100644 index 0000000..8abd2fb --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/NoticeWebSocketConfig.java @@ -0,0 +1,23 @@ +package com.cbsd.universaltestsoftware_client.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +@Configuration +@EnableWebSocket +public class NoticeWebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + // 注册WebSocket处理器,映射路径为/ws/chat + registry.addHandler(noticeWebSocketHandler(), "/ws/notice") + .setAllowedOrigins("*"); // 允许所有来源,生产环境应指定具体域名 + } + + @Bean + public NoticeWebSocketHandler noticeWebSocketHandler() { + return new NoticeWebSocketHandler(); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/NoticeWebSocketHandler.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/NoticeWebSocketHandler.java new file mode 100644 index 0000000..92cef0d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/NoticeWebSocketHandler.java @@ -0,0 +1,74 @@ +package com.cbsd.universaltestsoftware_client.websocket; + +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class NoticeWebSocketHandler extends TextWebSocketHandler { + + // 存储所有连接的会话,key为"IP:端口",value为WebSocketSession + private static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + // 获取客户端IP和端口 + String clientId = getClientId(session); + + // 存储会话 + sessions.put(clientId, session); + + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + // 获取发送者标识 + String senderId = getClientId(session); + + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + // 获取客户端标识 + String clientId = getClientId(session); + + // 移除会话 + sessions.remove(clientId); + + } + + /** + * 获取客户端唯一标识(IP:端口) + */ + private String getClientId(WebSocketSession session) { + InetSocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress != null) { + return remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort(); + } + return "unknown:" + session.getId(); + } + + /** + * 广播消息给所有连接的客户端 + */ + public void broadcast(String message) { + Set clientIds = sessions.keySet(); + + for (String clientId : clientIds) { + WebSocketSession session = sessions.get(clientId); + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(message)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/WebSocketController.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/WebSocketController.java new file mode 100644 index 0000000..3de4c55 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/WebSocketController.java @@ -0,0 +1,21 @@ +package com.cbsd.universaltestsoftware_client.websocket; + + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/websocket") +public class WebSocketController { + + @Autowired + private DataWebSocketHandler dataWebSocketHandler; + + // 广播消息给所有用户 + @GetMapping("/broadcast") + public String broadcast(@RequestParam String message) { + dataWebSocketHandler.broadcast(message); + return "已向所有用户广播消息"; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandClientMessage.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandClientMessage.java new file mode 100644 index 0000000..e4906e7 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandClientMessage.java @@ -0,0 +1,126 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import lombok.Data; +import lombok.Getter; + +import java.util.List; + +@Data +public class CommandClientMessage { + + /** + * 动作 + */ + public enum Operation { + /** + * 状态 + */ + STATE("state"), + /** + * 开始 + */ + START("start"), + /** + * 暂停 + */ + PAUSE("pause"), + /** + * 继续 + */ + RESUME("resume"), + /** + * 停止 + */ + STOP("stop"), + /** + * 重新开始 + */ + RESTART("restart"), + /** + * 单步 + */ + SINGLE_STEP("singleStep"), + /** + * 断点情况下单步 + */ + BREAK_POINT_SINGLE_STEP("breakPointSingleStep"), + /** + * 单次 + */ + ONCE("once"); + + @Getter + private final String value; + + Operation(String value) { + this.value = value; + } + + } + + /** + * 运行模式 + */ + public enum RunMode { + /** + * 顺序执行 + */ + SEQUENTIAL(1), + /** + * 并行执行 + */ + PARALLEL(2), + /** + * 单步执行 + */ + SINGLE(3); + + @Getter + private final int value; + + RunMode(int value) { + this.value = value; + } + + } + + /** + * 方案id + */ + private String schemeId; + /** + * 用例id + */ + private String invocationId; + + /** + * 操作 + */ + private String operation; + + /** + * 测试用例id集合 + */ + private List invocationIdList; + + /** + * 指令id列表 + */ + private List instructIdList; + + /** + * 运行模式 + */ + private int runMode; + + public String getRunModelLabel() { + if (runMode == RunMode.SEQUENTIAL.value) { + return "顺序执行"; + } else if (runMode == RunMode.PARALLEL.value) { + return "并行执行"; + } else if (runMode == RunMode.SINGLE.value) { + return "单步执行"; + } + return ""; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandRunMessage.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandRunMessage.java new file mode 100644 index 0000000..14dfbb8 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandRunMessage.java @@ -0,0 +1,64 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommandRunMessage { + + private String schemeId; + + private String invocationId; + + private Instruct instruct; + + private int runMode; + /** + * 状态 1.开始运行 2.运行中 3.运行结束 4.运行失败 + */ + private int commandStatus; + + /** + * 运行状态 0.未运行 1.运行中 2.暂停 3.debug 4.运行结束 + */ + private int runStatus; + + /** + * 运行模式 + */ + public enum RunStatus { + /** + * 未运行 + */ + NOT_RUN(0), + /** + * 运行中 + */ + RUNNING(1), + /** + * 暂停 + */ + PAUSE(2), + /** + * debug + */ + DEBUG(3), + /** + * 方案运行结束 + */ + COMPLETE(4); + + @Getter + private final int value; + + RunStatus(int value) { + this.value = value; + } + + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandWebSocketConfig.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandWebSocketConfig.java new file mode 100644 index 0000000..32ae6f5 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandWebSocketConfig.java @@ -0,0 +1,42 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import com.cbsd.universaltestsoftware_client.service.impl.ChannelDataServiceImpl; +import com.cbsd.universaltestsoftware_client.service.impl.InstructServiceImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +import javax.annotation.Resource; + + +@Configuration +@EnableWebSocket +public class CommandWebSocketConfig implements WebSocketConfigurer { + + @Resource + private InstructServiceImpl instructService; + + @Resource + private ChannelDataServiceImpl channelDataService; + + /** + * 指令文件路径 + */ + @Value("${cbsd.commandRootPath}") + private String commandRootPath; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + // 注册WebSocket处理器,映射路径为/ws/chat + registry.addHandler(commandWebSocketHandler(), "/ws/command") + .setAllowedOrigins("*"); // 允许所有来源,生产环境应指定具体域名 + } + + @Bean + public CommandWebSocketHandler commandWebSocketHandler() { + return new CommandWebSocketHandler(instructService, channelDataService, commandRootPath); + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandWebSocketHandler.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandWebSocketHandler.java new file mode 100644 index 0000000..f9dde9d --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/CommandWebSocketHandler.java @@ -0,0 +1,160 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import com.alibaba.fastjson.JSON; +import com.cbsd.universaltestsoftware_client.service.impl.ChannelDataServiceImpl; +import com.cbsd.universaltestsoftware_client.service.impl.InstructServiceImpl; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import lombok.extern.log4j.Log4j2; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +public class CommandWebSocketHandler extends TextWebSocketHandler implements SchemeRunThread.OnSchemeRunListener{ + + private final InstructServiceImpl instructService; + + private final ChannelDataServiceImpl channelDataService; + + /** + * 指令文件路径 + */ + private final String commandRootPath; + + /** + * 方案运行县城map,key方案id,value为运行线程 + */ + private final Map schemeThreadMap = new ConcurrentHashMap<>(); + + + public CommandWebSocketHandler(InstructServiceImpl instructService, ChannelDataServiceImpl channelDataService, String commandRootPath) { + this.instructService = instructService; + this.channelDataService = channelDataService; + this.commandRootPath = commandRootPath; + } + + // 存储所有连接的会话,key为"IP:端口",value为WebSocketSession + private static final Map sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + // 获取客户端IP和端口 + String clientId = getClientId(session); + // 存储会话 + sessions.put(clientId, session); + } + + @Override + public void onComplete(String schemeId) { + if (schemeThreadMap.get(schemeId) != null){ + schemeThreadMap.get(schemeId).stopRun(); + } + schemeThreadMap.remove(schemeId); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + // 获取发送者标识 + String senderId = getClientId(session); + String payload = message.getPayload(); + try { + CommandClientMessage clientMessage = JSON.parseObject(payload, CommandClientMessage.class); + String schemeId = clientMessage.getSchemeId(); + if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.STATE.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + broadcast(JSON.toJSONString(schemeRunThread.getRunState())); + } + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.START.getValue())) { + if (schemeThreadMap.get(schemeId) != null) { + return; + } + SchemeRunThread schemeRunThread = new SchemeRunThread(clientMessage, instructService, channelDataService, commandRootPath); + schemeRunThread.setOnSchemeRunListener(this); + schemeThreadMap.put(schemeId, schemeRunThread); + schemeRunThread.start(); + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.PAUSE.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + schemeRunThread.pauseRun(); + } + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.RESUME.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + schemeRunThread.resumeRun(clientMessage.getInvocationId()); + } + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.STOP.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + schemeRunThread.stopRun(); + } + schemeThreadMap.remove(schemeId); + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.RESTART.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + schemeRunThread.restartRun(); + } + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.SINGLE_STEP.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + schemeRunThread.singleStepRun(); + } + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.BREAK_POINT_SINGLE_STEP.getValue())) { + SchemeRunThread schemeRunThread = schemeThreadMap.get(schemeId); + if (schemeRunThread != null) { + schemeRunThread.breakPointSingleStepRun(clientMessage.getInvocationId()); + } + } else if (Objects.equals(clientMessage.getOperation(), CommandClientMessage.Operation.ONCE.getValue())) { + + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + // 获取客户端标识 + String clientId = getClientId(session); + // 移除会话 + sessions.remove(clientId); + } + + /** + * 获取客户端唯一标识(IP:端口) + */ + private String getClientId(WebSocketSession session) { + InetSocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress != null) { + return remoteAddress.getAddress().getHostAddress() + ":" + remoteAddress.getPort(); + } + return "unknown:" + session.getId(); + } + + /** + * 广播消息给所有连接的客户端 + */ + public void broadcast(String message) { + Set clientIds = sessions.keySet(); + for (String clientId : clientIds) { + WebSocketSession session = sessions.get(clientId); + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(message)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/InvocationRunThread.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/InvocationRunThread.java new file mode 100644 index 0000000..b2604f9 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/InvocationRunThread.java @@ -0,0 +1,633 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import com.cbsd.client.branch.IBranchConditionInterface; +import com.cbsd.client.pretreatment.CalculateUtils; +import com.cbsd.client.pretreatment.IPreTreatmentInterface; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.script.ScriptManager; +import com.cbsd.universaltestsoftware_client.service.impl.ChannelDataServiceImpl; +import com.cbsd.universaltestsoftware_client.service.impl.InstructServiceImpl; +import com.cbsd.universaltestsoftware_client.util.DataUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.ResultCode; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class InvocationRunThread extends Thread { + + public interface OnInvocationRunListener { + void onInvocationComplete(String invocationId); + + void onInvocationRunning(CommandRunMessage runMessage); + + void onInvocationDebug(String invocationId); + } + + @Setter + private OnInvocationRunListener onInvocationRunListener; + + /** + * 用例id + */ + private final String invocationId; + /** + * 指令运行参数 + */ + private final CommandClientMessage clientMessage; + + private final InstructServiceImpl instructService; + + private final ChannelDataServiceImpl channelDataService; + /** + * 指令文件路径 + */ + private final String commandRootPath; + /** + * 正在运行的用例指令列表 + */ + private List runningInstructList; + /** + * 正在运行的指令索引 + */ + private int runningInstructIndex = 0; + /** + * 正在运行的指令运行次数 + */ + private int runningInstructRunCount = 1; + /** + * 正在运行的指令组list + */ + private final Map runningInstructGroupMap = new LinkedHashMap<>(); + /** + * 处理延时任务 + */ + private final CompositeDisposable disposable = new CompositeDisposable(); + /** + * 是否跳过debug 1.是 0.否 + */ + private int isSkipDebug; + /** + * 是否是单步调试 + */ + private boolean isDebugSingleStep = false; + /** + * 是否是第一次进入断点 + */ + private boolean isFirstEnterBreakPoint = false; + /** + * 运行状态 0.未运行 1.运行中 2.暂停 3.debug 4.运行结束 + */ + private int runStatus = CommandRunMessage.RunStatus.NOT_RUN.getValue(); + + public InvocationRunThread(String invocationId, CommandClientMessage clientMessage, InstructServiceImpl instructService, ChannelDataServiceImpl channelDataService, String commandRootPath) { + this.invocationId = invocationId; + this.clientMessage = clientMessage; + this.instructService = instructService; + this.channelDataService = channelDataService; + this.commandRootPath = commandRootPath; + + initInstructList(); + } + + private void initInstructList() { + //查询该用例的所有指令 + runningInstructList = instructService.getInstructListByInvocationId(invocationId); + if (runningInstructList != null) { + if (clientMessage.getInstructIdList() != null && !clientMessage.getInstructIdList().isEmpty()) { + //将选择的指令的命中次数置为0 + instructService.resetHitCountByInstructIdList(clientMessage.getInstructIdList()); + if (runningInstructList != null) { + runningInstructList.forEach(instruct -> { + instruct.setNeedRun(clientMessage.getInstructIdList().contains(instruct.getInstructId())); + }); + } + } else { + //将该用例的命中次数置为0 + instructService.resetHitCountByInvocation(invocationId); + if (runningInstructList != null) { + runningInstructList.forEach(instruct -> { + instruct.setNeedRun(true); + }); + } + } + + } + } + + private int times = 0; + + @Override + public void run() { + startRun(); + } + + /** + * 开始运行 + */ + public void startRun() { + runStatus = CommandRunMessage.RunStatus.RUNNING.getValue(); + runningInstructIndex = 0; + runningInstructRunCount = 1; + isFirstEnterBreakPoint = false; + runInstruct(runningInstructList.get(runningInstructIndex)); + } + + /** + * 暂停运行 + */ + public void pauseRun() { + if (disposable.size() > 0 && !disposable.isDisposed()) { + disposable.clear(); + } + runStatus = CommandRunMessage.RunStatus.PAUSE.getValue(); + } + + /** + * 继续运行 + */ + public void resumeRun() { + runStatus = CommandRunMessage.RunStatus.RUNNING.getValue(); + this.isSkipDebug = 1; + this.isDebugSingleStep = false; + + if (isFirstEnterBreakPoint) { + runningInstructIndex--; + isFirstEnterBreakPoint = false; + } + Instruct runInstruct = runningInstructList.get(runningInstructIndex); + if (runningInstructRunCount < runInstruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(runInstruct); + } else { + runNextInstructIter(); + } + } + + /** + * 停止运行 + */ + public void stopRun() { + if (disposable.size() > 0 && !disposable.isDisposed()) { + disposable.clear(); + } + this.isDebugSingleStep = false; + runStatus = CommandRunMessage.RunStatus.NOT_RUN.getValue(); + } + + /** + * 重新运行 + */ + public void restartRun() { + stopRun(); + startRun(); + } + + /** + * 单步运行 + */ + public void singleStepRun() { + Instruct runInstruct = runningInstructList.get(runningInstructIndex); + if (runningInstructRunCount < runInstruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(runInstruct); + } else { + runNextInstructIter(); + } + } + + /** + * 断点单步运行 + */ + public void breakPointSingleStepRun() { + this.isSkipDebug = 1; + this.isDebugSingleStep = true; + this.runStatus = CommandRunMessage.RunStatus.DEBUG.getValue(); + if (isFirstEnterBreakPoint) { + runningInstructIndex--; + isFirstEnterBreakPoint = false; + } + Instruct runInstruct = runningInstructList.get(runningInstructIndex); + if (runningInstructRunCount < runInstruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(runInstruct); + } else { + runNextInstructIter(); + } + } + + /** + * 获取当前运行状态 + */ + public CommandRunMessage getRunState() { + Instruct runningInstruct = null; + if (runningInstructList.size() > runningInstructIndex) { + runningInstruct = runningInstructList.get(runningInstructIndex); + } + CommandRunMessage stateMsg = new CommandRunMessage(); + stateMsg.setRunStatus(runStatus); + stateMsg.setInvocationId(invocationId); + stateMsg.setInstruct(runningInstruct); + stateMsg.setSchemeId(clientMessage.getSchemeId()); + stateMsg.setRunMode(clientMessage.getRunMode()); + return stateMsg; + } + + /** + * 运行指令 + * + * @param runInstruct 运行的指令 + */ + private void runInstruct(Instruct runInstruct) { + if (runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { + return; + } + if (!runInstruct.isNeedRun()) { + runNextInstructIter(); // 改为迭代版的下一条指令执行 + return; + } + //获取该条指令最新数据 + Instruct instruct = instructService.getById(runInstruct.getInstructId()); + if (instruct.getStatus() == 0) { + //禁用 + runNextInstructIter(); + return; + } + if (instruct.getInstructType() == 2) { + //指令组 + if (!runningInstructGroupMap.containsKey(instruct.getInstructId())) { + runningInstructGroupMap.put(instruct.getInstructId(), 0); + } + runNextInstructIter(); + return; + } + instruct.setNeedRun(true); + if (onInvocationRunListener != null) { + CommandRunMessage stateMsg = getRunState(); + stateMsg.setInstruct(instruct); + stateMsg.setCommandStatus(1); + onInvocationRunListener.onInvocationRunning(stateMsg); + } + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue() + && instruct.getBreakPoint() != null && instruct.getBreakPoint() == 1) { + if (this.isSkipDebug == 0) { + if (onInvocationRunListener != null) { + onInvocationRunListener.onInvocationDebug(invocationId); + } + isFirstEnterBreakPoint = true; + this.runStatus = CommandRunMessage.RunStatus.DEBUG.getValue(); + return; + } + } + this.isSkipDebug = 0; + // 执行指令发送逻辑(核心执行逻辑) + executeInstructAction(instruct); + } + + // 2. 新增:指令执行的核心动作(原 runInstruct 中的发送+延时逻辑) + private void executeInstructAction(Instruct instruct) { + if (disposable.size() > 0 && !disposable.isDisposed()) { + disposable.clear(); + } + // 延时/无延时执行指令(保留原有 RxJava 逻辑,但回调改为迭代) + if (instruct.getLeadTime() != null && instruct.getLeadTime() > 0) { + disposable.add(Observable.timer(instruct.getLeadTime(), TimeUnit.MILLISECONDS) + .subscribe(lead -> { + sendInstruct(instruct); + handleTailTimeAndRepeat(instruct); // 处理尾延时和重复逻辑 + }, e -> log.error("Lead time error", e))); + } else { + sendInstruct(instruct); + handleTailTimeAndRepeat(instruct); // 处理尾延时和重复逻辑 + } + } + + // 3. 新增:处理尾延时和重复次数判断(迭代模式) + private void handleTailTimeAndRepeat(Instruct instruct) { + if (instruct.getTailTime() == null || instruct.getTailTime() < 10) { + instruct.setTailTime(10); + } + disposable.add(Observable.timer(instruct.getTailTime(), TimeUnit.MILLISECONDS) + .subscribe(tail -> { + if (checkBranchCondition(instruct)) { + judgeRepeatCountIter(instruct); // 迭代版重复次数判断 + } + }, e -> log.error("Tail time error", e))); + } + + // 4. 迭代版重复次数判断(核心修改:不再递归调用 runInstruct,而是更新状态后重新执行循环) + private void judgeRepeatCountIter(Instruct instruct) { + if (clientMessage.getRunMode() == CommandClientMessage.RunMode.SINGLE.getValue() || isDebugSingleStep) { + return; // 单步调试逻辑保留 + } + if (instruct.getRepeatCount() != null && instruct.getRepeatCount() > 0) { + if (runningInstructRunCount < instruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(instruct); // 此处看似递归,但实际是单次调用,栈会被释放(因为 RxJava 回调是异步,栈已清空) + } else { + runNextInstructIter(); // 执行下一条指令 + } + } else { + runNextInstructIter(); // 无重复次数,执行下一条 + } + } + + // 5. 迭代版执行下一条指令(统一循环入口) + private void runNextInstructIter() { + if (!runningInstructGroupMap.isEmpty()) { + runInstructGroupJudgeRepeatCount(); // 指令组逻辑保留,需确保内部无递归 + } else { + if (runningInstructIndex < runningInstructList.size() - 1) { + runningInstructRunCount = 1; + runningInstructIndex++; + Instruct nextInstruct = runningInstructList.get(runningInstructIndex); + runInstruct(nextInstruct); // 单次调用,无递归栈累积 + } else { + if (onInvocationRunListener != null) { + onInvocationRunListener.onInvocationComplete(invocationId); + } + } + } + } + + /** + * 发送指令 + * + * @param instruct 指令 + */ + private void sendInstruct(Instruct instruct) { + if (runStatus == CommandRunMessage.RunStatus.NOT_RUN.getValue() + || runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { + return; + } + Result result = instructService.sendInstruct(getRunState()); + if (result.getCode() != ResultCode.OK) { + //指令由于服务端未连接等原因发送失败,向前端推送失败消息 + if (onInvocationRunListener != null) { + CommandRunMessage stateMsg = getRunState(); + stateMsg.setInstruct(instruct); + stateMsg.setCommandStatus(4); + onInvocationRunListener.onInvocationRunning(stateMsg); + } + } + } + + /** + * 判断分支条件 + * + * @param instruct 指令 + */ + private boolean checkBranchCondition(Instruct instruct) { + if (runStatus == CommandRunMessage.RunStatus.NOT_RUN.getValue() + || runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { + return false; + } + if (StringUtils.isNotBlank(instruct.getBranchConditionScriptName())) { + try { + String originalCommand = instruct.getInstructText(); + if (StringUtils.isBlank(originalCommand)) { + //判断指令文件是否为空 + if (StringUtils.isNotBlank(instruct.getInstructDataFile())) { + File commandFile = new File(commandRootPath, instruct.getInstructDataFile()); + if (commandFile.exists()) { + try { + originalCommand = String.join("", Files.readAllLines(commandFile.toPath())); + } catch (Exception e) { + //按照二进制读取 + byte[] bytes = Files.readAllBytes(commandFile.toPath()); + originalCommand = DataUtils.byteToHex(bytes); + } + } + } + } + + String preTreatmentCommand = originalCommand; + Map latestDataMap = channelDataService.getChannelLatestData(instruct.getChannelId()); + //获取预处理脚本 + if (StringUtils.isNotBlank(instruct.getPreProcessScriptName())) { + IPreTreatmentInterface preTreatmentInterface = (IPreTreatmentInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IPreTreatmentInterface.getValue(), instruct.getPreProcessScriptName()).newInstance(); + //查询通道数据 + String pretreatmentResult = preTreatmentInterface.pretreatment(originalCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap, new CalculateUtils()); + if (StringUtils.isNotBlank(pretreatmentResult)) { + preTreatmentCommand = pretreatmentResult; + } + } + + IBranchConditionInterface branchConditionInterface = (IBranchConditionInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IBranchConditionInterface.getValue(), instruct.getBranchConditionScriptName()).newInstance(); + //查询通道数据 + String jumpInstructName = branchConditionInterface.branchCondition(originalCommand, preTreatmentCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap); + if (StringUtils.isNotBlank(jumpInstructName)) { + for (int i = 0; i < runningInstructList.size(); i++) { + Instruct jumpInstruct = runningInstructList.get(i); + if (jumpInstruct.getInstructName().equals(jumpInstructName)) { + //需判断跳转的指令是否是指令组下的指令, parentEncoding 不为0且能找到指令组 + Instruct instructGroup = queryInstructGroupByEncoding(jumpInstruct.getParentEncoding()); + if (instructGroup != null) { + //移除上一个组 + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + runningInstructGroupMap.remove(lastEntry.getKey()); + //添加新的组 + if (!runningInstructGroupMap.containsKey(instructGroup.getInstructId())) { + runningInstructGroupMap.put(instructGroup.getInstructId(), 1); + } + } + runningInstructIndex = i; + runningInstructRunCount = 1; + jumpInstruct.setNeedRun(true); + runInstruct(jumpInstruct); + return false; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 根据父级编码查询父级指令组 + * + * @param parentEncoding 父级编码 + */ + private Instruct queryInstructGroupByEncoding(String parentEncoding) { + if (StringUtils.isNotBlank(parentEncoding) && !"0".equals(parentEncoding)) { + for (Instruct instruct : runningInstructList) { + if (instruct.getEncoding().equals(parentEncoding)) { + return instruct; + } + } + } + return null; + } + + /** + * 判断指令运行次数 + * + * @param instruct 指令 + */ + private void runInstructJudgeRepeatCount(Instruct instruct) { + if (instruct.getRepeatCount() != null && instruct.getRepeatCount() > 0) { + if (runningInstructRunCount < instruct.getRepeatCount()) { + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue()) { + if (!isDebugSingleStep) { + runningInstructRunCount++; + runInstruct(instruct); + } + } + } else { + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue()) { + if (!isDebugSingleStep) { + runNextInstructIter(); + } + } + } + } else { + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue()) { + if (!isDebugSingleStep) { + runNextInstructIter(); + } + } + } + } + + /** + * 判断指令组重复次数 + */ + private void runInstructGroupJudgeRepeatCount() { + //当前指令 + Instruct currentInstruct = runningInstructList.get(runningInstructIndex); + if (currentInstruct.getInstructType() == 2) { + //是指令组 + //判断当前组执行次数 + if (checkGroupRunCount()) { + //需要继续执行, 即执行组第一条指令 + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + Instruct lastRunInstructGroup = instructService.getById(lastEntry.getKey()); + List groupInstructList = getGroupInstructList(lastRunInstructGroup); + if (groupInstructList.isEmpty()) { + //组下面没有指令可执行,正常执行下一条指令 + runNextInstructIter(); + } else { + Instruct groupFirstInstruct = groupInstructList.get(0); + for (int i = 0; i < runningInstructList.size(); i++) { + if (runningInstructList.get(i).getInstructId().equals(groupFirstInstruct.getInstructId())) { + runningInstructIndex = i; + runningInstructRunCount = 1; + runInstruct(runningInstructList.get(runningInstructIndex)); + return; + } + } + } + } else { + //执行完成, 正常执行下一条指令 + runNextInstructIter(); + } + } else { + //是指令 + //判断当前指令是否是当前组的最后一条 + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + Instruct lastRunInstructGroup = instructService.getById(lastEntry.getKey()); + List groupInstructList = getGroupInstructList(lastRunInstructGroup); + if (currentInstruct.getInstructId().equals(groupInstructList.get(groupInstructList.size() - 1).getInstructId())) { + //是当前组最后一条指令 + //判断当前组执行次数 + if (lastEntry.getValue() < lastRunInstructGroup.getRepeatCount()) { + updateGroupRunCount(lastEntry.getKey(), lastEntry.getValue() + 1); + Instruct groupFirstInstruct = groupInstructList.get(0); + for (int i = 0; i < runningInstructList.size(); i++) { + if (runningInstructList.get(i).getInstructId().equals(groupFirstInstruct.getInstructId())) { + runningInstructIndex = i; + runningInstructRunCount = 1; + runInstruct(runningInstructList.get(runningInstructIndex)); + return; + } + } + } else { + runningInstructGroupMap.remove(lastEntry.getKey()); + runNextInstructIter(); + } + } else { + if (runningInstructIndex < runningInstructList.size() - 1) { + runningInstructRunCount = 1; + runningInstructIndex++; + Instruct nextInstruct = runningInstructList.get(runningInstructIndex); + runInstruct(nextInstruct); // 单次调用,无递归栈累积 + } else { + if (onInvocationRunListener != null) { + onInvocationRunListener.onInvocationComplete(invocationId); + } + } + } + } + } + + /** + * 检查指令组执行次数 + * + * @return 是否继续执行 + */ + private boolean checkGroupRunCount() { + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + Instruct lastGroup = instructService.getById(lastEntry.getKey()); + if (lastEntry.getValue() < lastGroup.getRepeatCount()) { + //还要执行, 将运行次数加1 + updateGroupRunCount(lastEntry.getKey(), lastEntry.getValue() + 1); + return true; + } else { + //执行完成 + runningInstructGroupMap.remove(lastEntry.getKey()); + if (!runningInstructGroupMap.isEmpty()) { + return checkGroupRunCount(); + } else { + //整个组执行完了 + return false; + } + } + } + + /** + * 更新指令组运行次数 + * + * @param instructId 指令id + * @param runCount 运行次数 + */ + private void updateGroupRunCount(String instructId, int runCount) { + runningInstructGroupMap.replace(instructId, runCount); + } + + /** + * 获取运行中map的最后一个组 + */ + private Map.Entry getRunningInstructGroupMapLast() { + List> entries = new ArrayList<>(runningInstructGroupMap.entrySet()); + return entries.get(entries.size() - 1); + } + + /** + * 获取指令组内指令 + * + * @param instructGroup 指令组 + */ + private List getGroupInstructList(Instruct instructGroup) { + List instructList = new ArrayList<>(); + runningInstructList.forEach(instruct -> { + if (instruct.getParentEncoding().startsWith(instructGroup.getEncoding())) { + instructList.add(instruct); + } + }); + return instructList; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/InvocationRunThreadBack.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/InvocationRunThreadBack.java new file mode 100644 index 0000000..69e6e29 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/InvocationRunThreadBack.java @@ -0,0 +1,631 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import com.cbsd.client.branch.IBranchConditionInterface; +import com.cbsd.client.pretreatment.CalculateUtils; +import com.cbsd.client.pretreatment.IPreTreatmentInterface; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.script.ScriptManager; +import com.cbsd.universaltestsoftware_client.service.impl.ChannelDataServiceImpl; +import com.cbsd.universaltestsoftware_client.service.impl.InstructServiceImpl; +import com.cbsd.universaltestsoftware_client.util.DataUtils; +import com.cbsd.universaltestsoftware_client.util.Result; +import com.cbsd.universaltestsoftware_client.util.ResultCode; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.disposables.CompositeDisposable; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class InvocationRunThreadBack extends Thread { + + public interface OnInvocationRunListener { + void onInvocationComplete(String invocationId); + + void onInvocationRunning(CommandRunMessage runMessage); + + void onInvocationDebug(String invocationId); + } + + @Setter + private OnInvocationRunListener onInvocationRunListener; + + /** + * 用例id + */ + private final String invocationId; + /** + * 指令运行参数 + */ + private final CommandClientMessage clientMessage; + + private final InstructServiceImpl instructService; + + private final ChannelDataServiceImpl channelDataService; + /** + * 指令文件路径 + */ + private final String commandRootPath; + /** + * 正在运行的用例指令列表 + */ + private List runningInstructList; + /** + * 正在运行的指令索引 + */ + private int runningInstructIndex = 0; + /** + * 正在运行的指令运行次数 + */ + private int runningInstructRunCount = 1; + /** + * 正在运行的指令组list + */ + private final Map runningInstructGroupMap = new LinkedHashMap<>(); + /** + * 处理延时任务 + */ + private final CompositeDisposable disposable = new CompositeDisposable(); + /** + * 是否跳过debug 1.是 0.否 + */ + private int isSkipDebug; + /** + * 是否是单步调试 + */ + private boolean isDebugSingleStep = false; + /** + * 是否是第一次进入断点 + */ + private boolean isFirstEnterBreakPoint = false; + /** + * 运行状态 0.未运行 1.运行中 2.暂停 3.debug 4.运行结束 + */ + private int runStatus = CommandRunMessage.RunStatus.NOT_RUN.getValue(); + + public InvocationRunThreadBack(String invocationId, CommandClientMessage clientMessage, InstructServiceImpl instructService, ChannelDataServiceImpl channelDataService, String commandRootPath) { + this.invocationId = invocationId; + this.clientMessage = clientMessage; + this.instructService = instructService; + this.channelDataService = channelDataService; + this.commandRootPath = commandRootPath; + + initInstructList(); + } + + private void initInstructList() { + //查询该用例的所有指令 + runningInstructList = instructService.getInstructListByInvocationId(invocationId); + if (runningInstructList != null) { + if (clientMessage.getInstructIdList() != null && !clientMessage.getInstructIdList().isEmpty()) { + //将选择的指令的命中次数置为0 + instructService.resetHitCountByInstructIdList(clientMessage.getInstructIdList()); + if (runningInstructList != null) { + runningInstructList.forEach(instruct -> { + instruct.setNeedRun(clientMessage.getInstructIdList().contains(instruct.getInstructId())); + }); + } + } else { + //将该用例的命中次数置为0 + instructService.resetHitCountByInvocation(invocationId); + if (runningInstructList != null) { + runningInstructList.forEach(instruct -> { + instruct.setNeedRun(true); + }); + } + } + + } + } + + private int times = 0; + + @Override + public void run() { + startRun(); +// while (times < 100){ +// times ++; +// System.out.println("========= times:" + times); +// } + } + + /** + * 开始运行 + */ + public void startRun() { + runStatus = CommandRunMessage.RunStatus.RUNNING.getValue(); + runningInstructIndex = 0; + runningInstructRunCount = 1; + isFirstEnterBreakPoint = false; + runInstruct(runningInstructList.get(runningInstructIndex)); + } + + /** + * 暂停运行 + */ + public void pauseRun() { + if (disposable.size() > 0 && !disposable.isDisposed()) { + disposable.clear(); + } + runStatus = CommandRunMessage.RunStatus.PAUSE.getValue(); + } + + /** + * 继续运行 + */ + public void resumeRun() { + runStatus = CommandRunMessage.RunStatus.RUNNING.getValue(); + this.isSkipDebug = 1; + this.isDebugSingleStep = false; + + if (isFirstEnterBreakPoint) { + runningInstructIndex--; + isFirstEnterBreakPoint = false; + } + Instruct runInstruct = runningInstructList.get(runningInstructIndex); + if (runningInstructRunCount < runInstruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(runInstruct); + } else { + runNextInstruct(); + } + } + + /** + * 停止运行 + */ + public void stopRun() { + if (disposable.size() > 0 && !disposable.isDisposed()) { + disposable.clear(); + } + this.isDebugSingleStep = false; + runStatus = CommandRunMessage.RunStatus.NOT_RUN.getValue(); + } + + /** + * 重新运行 + */ + public void restartRun() { + stopRun(); + startRun(); + } + + /** + * 单步运行 + */ + public void singleStepRun() { + Instruct runInstruct = runningInstructList.get(runningInstructIndex); + if (runningInstructRunCount < runInstruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(runInstruct); + } else { + runNextInstruct(); + } + } + + /** + * 断点单步运行 + */ + public void breakPointSingleStepRun() { + this.isSkipDebug = 1; + this.isDebugSingleStep = true; + this.runStatus = CommandRunMessage.RunStatus.DEBUG.getValue(); + if (isFirstEnterBreakPoint) { + runningInstructIndex--; + isFirstEnterBreakPoint = false; + } + Instruct runInstruct = runningInstructList.get(runningInstructIndex); + if (runningInstructRunCount < runInstruct.getRepeatCount()) { + runningInstructRunCount++; + runInstruct(runInstruct); + } else { + runNextInstruct(); + } + } + + /** + * 获取当前运行状态 + */ + public CommandRunMessage getRunState() { + Instruct runningInstruct = null; + if (runningInstructList.size() > runningInstructIndex) { + runningInstruct = runningInstructList.get(runningInstructIndex); + } + CommandRunMessage stateMsg = new CommandRunMessage(); + stateMsg.setRunStatus(runStatus); + stateMsg.setInvocationId(invocationId); + stateMsg.setInstruct(runningInstruct); + stateMsg.setRunMode(clientMessage.getRunMode()); + return stateMsg; + } + + /** + * 运行指令 + * + * @param runInstruct 运行的指令 + */ + private void runInstruct(Instruct runInstruct) { +// if (isPause) { +// return; +// } + if (runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { + return; + } + if (!runInstruct.isNeedRun()) { + //不需要运行,运行下一个 + runNextInstruct(); + return; + } + //获取该条指令最新数据 + Instruct instruct = instructService.getById(runInstruct.getInstructId()); + if (instruct.getStatus() == 0) { + //禁用 + runNextInstruct(); + return; + } + if (instruct.getInstructType() == 2) { + //指令组 + if (!runningInstructGroupMap.containsKey(instruct.getInstructId())) { + runningInstructGroupMap.put(instruct.getInstructId(), 0); + } + runNextInstruct(); + return; + } + instruct.setNeedRun(true); + if (onInvocationRunListener != null) { + CommandRunMessage stateMsg = getRunState(); + stateMsg.setInstruct(instruct); + stateMsg.setCommandStatus(1); + onInvocationRunListener.onInvocationRunning(stateMsg); + } + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue() + && instruct.getBreakPoint() != null && instruct.getBreakPoint() == 1) { + if (this.isSkipDebug == 0) { + if (onInvocationRunListener != null) { + onInvocationRunListener.onInvocationDebug(invocationId); + } + isFirstEnterBreakPoint = true; + this.runStatus = CommandRunMessage.RunStatus.DEBUG.getValue(); + return; + } + } + this.isSkipDebug = 0; + if (disposable.size() > 0 && !disposable.isDisposed()) { + disposable.clear(); + } + if (instruct.getLeadTime() != null && instruct.getLeadTime() > 0) { + disposable.add(Observable.timer(instruct.getLeadTime(), TimeUnit.MILLISECONDS) + .subscribe(lead -> { + sendInstruct(instruct); + if (instruct.getTailTime() != null && instruct.getTailTime() > 0) { + disposable.add(Observable.timer(instruct.getTailTime(), TimeUnit.MILLISECONDS) + .subscribe(tail -> { + if (checkBranchCondition(instruct)) { + runInstructJudgeRepeatCount(instruct); + } + }, e -> { + + })); + } else { + if (checkBranchCondition(instruct)) { + runInstructJudgeRepeatCount(instruct); + } + } + }, e -> { + + })); + } else { + sendInstruct(instruct); + if (instruct.getTailTime() != null && instruct.getTailTime() > 0) { + disposable.add(Observable.timer(instruct.getTailTime(), TimeUnit.MILLISECONDS) + .subscribe(tail -> { + if (runStatus == CommandRunMessage.RunStatus.NOT_RUN.getValue() + || runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { + return; + } + if (checkBranchCondition(instruct)) { + runInstructJudgeRepeatCount(instruct); + } + }, e -> { + + })); + } else { + if (runStatus == CommandRunMessage.RunStatus.NOT_RUN.getValue() + || runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { + return; + } + if (checkBranchCondition(instruct)) { + runInstructJudgeRepeatCount(instruct); + } + } + } + +// if (instruct.getLeadTime() != null && instruct.getLeadTime() > 0) { +// CountDownLatch leadLatch = new CountDownLatch(1); +// disposable.add(Observable.timer(instruct.getLeadTime(), TimeUnit.MILLISECONDS) +// .doOnNext(lead -> leadLatch.countDown()) +// .doOnError(e -> leadLatch.countDown()).subscribe()); +// try { +// leadLatch.await(); // 等待直到计时器完成或超时(如果有的话) +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// sendInstruct(instruct); +// if (instruct.getTailTime() != null && instruct.getTailTime() > 0) { +// CountDownLatch tailLatch = new CountDownLatch(1); +// disposable.add(Observable.timer(instruct.getTailTime(), TimeUnit.MILLISECONDS) +// .doOnNext(tail -> tailLatch.countDown()) +// .doOnError(e -> tailLatch.countDown()).subscribe()); +// try { +// tailLatch.await(); // 等待直到计时器完成或超时(如果有的话) +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// if (runStatus == CommandRunMessage.RunStatus.NOT_RUN.getValue() +// || runStatus == CommandRunMessage.RunStatus.PAUSE.getValue()) { +// return; +// } +// if (checkBranchCondition(instruct)) { +// runInstructJudgeRepeatCount(instruct); +// } + } + + /** + * 运行下一条指令 + */ + private void runNextInstruct() { + if (!runningInstructGroupMap.isEmpty()) { + runInstructGroupJudgeRepeatCount(); + } else { + if (runningInstructIndex < runningInstructList.size() - 1) { + runningInstructRunCount = 1; + runningInstructIndex++; + Instruct instruct = runningInstructList.get(runningInstructIndex); + runInstruct(instruct); + } else { + if (onInvocationRunListener != null) { + onInvocationRunListener.onInvocationComplete(invocationId); + } + } + } + } + + /** + * 发送指令 + * + * @param instruct 指令 + */ + private void sendInstruct(Instruct instruct) { + Result result = instructService.sendInstruct(getRunState()); + if (result.getCode() != ResultCode.OK) { + //指令由于服务端未连接等原因发送失败,向前端推送失败消息 + if (onInvocationRunListener != null) { + CommandRunMessage stateMsg = getRunState(); + stateMsg.setInstruct(instruct); + stateMsg.setCommandStatus(4); + onInvocationRunListener.onInvocationRunning(stateMsg); + } + } + } + + /** + * 判断分支条件 + * + * @param instruct 指令 + */ + private boolean checkBranchCondition(Instruct instruct) { + if (StringUtils.isNotBlank(instruct.getBranchConditionScriptName())) { + try { + String originalCommand = instruct.getInstructText(); + if (StringUtils.isBlank(originalCommand)) { + //判断指令文件是否为空 + if (StringUtils.isNotBlank(instruct.getInstructDataFile())) { + File commandFile = new File(commandRootPath, instruct.getInstructDataFile()); + if (commandFile.exists()) { + try { + originalCommand = String.join("", Files.readAllLines(commandFile.toPath())); + } catch (Exception e) { + //按照二进制读取 + byte[] bytes = Files.readAllBytes(commandFile.toPath()); + originalCommand = DataUtils.byteToHex(bytes); + } + } + } + } + + String preTreatmentCommand = originalCommand; + Map latestDataMap = channelDataService.getChannelLatestData(instruct.getChannelId()); + //获取预处理脚本 + if (StringUtils.isNotBlank(instruct.getPreProcessScriptName())) { + IPreTreatmentInterface preTreatmentInterface = (IPreTreatmentInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IPreTreatmentInterface.getValue(), instruct.getPreProcessScriptName()).newInstance(); + //查询通道数据 + String pretreatmentResult = preTreatmentInterface.pretreatment(originalCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap, new CalculateUtils()); + if (StringUtils.isNotBlank(pretreatmentResult)) { + preTreatmentCommand = pretreatmentResult; + } + } + + IBranchConditionInterface branchConditionInterface = (IBranchConditionInterface) ScriptManager.getScriptClass(ScriptManager.ScriptType.IBranchConditionInterface.getValue(), instruct.getBranchConditionScriptName()).newInstance(); + //查询通道数据 + String jumpInstructName = branchConditionInterface.branchCondition(originalCommand, preTreatmentCommand, instruct.getRepeatCount(), instruct.getHitCount(), instruct.getLastHitTime(), latestDataMap); + if (StringUtils.isNotBlank(jumpInstructName)) { + for (int i = 0; i < runningInstructList.size(); i++) { + Instruct jumpInstruct = runningInstructList.get(i); + if (jumpInstruct.getInstructName().equals(jumpInstructName)) { + runningInstructIndex = i; + runningInstructRunCount = 1; + jumpInstruct.setNeedRun(true); + runInstruct(jumpInstruct); + return false; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 判断指令运行次数 + * + * @param instruct 指令 + */ + private void runInstructJudgeRepeatCount(Instruct instruct) { + if (instruct.getRepeatCount() != null && instruct.getRepeatCount() > 0) { + System.out.println("运行次数:" + runningInstructRunCount + ", 重复次数: " + instruct.getRepeatCount() + ", 运行模式: " + clientMessage.getRunMode() + ", isDebugSingleStep: " + isDebugSingleStep); + if (runningInstructRunCount < instruct.getRepeatCount()) { + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue()) { + if (!isDebugSingleStep) { + runningInstructRunCount++; + runInstruct(instruct); + } + } + } else { + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue()) { + if (!isDebugSingleStep) { + runNextInstruct(); + } + } + } + } else { + if (clientMessage.getRunMode() != CommandClientMessage.RunMode.SINGLE.getValue()) { + if (!isDebugSingleStep) { + runNextInstruct(); + } + } + } + } + + /** + * 判断指令组重复次数 + */ + private void runInstructGroupJudgeRepeatCount() { + //当前指令 + Instruct currentInstruct = runningInstructList.get(runningInstructIndex); + if (currentInstruct.getInstructType() == 2) { + //是指令组 + //判断当前组执行次数 + if (checkGroupRunCount()) { + //需要继续执行, 即执行组第一条指令 + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + Instruct lastRunInstructGroup = instructService.getById(lastEntry.getKey()); + List groupInstructList = getGroupInstructList(lastRunInstructGroup); + if (groupInstructList.isEmpty()) { + //组下面没有指令可执行,正常执行下一条指令 + runNextInstruct(); + } else { + Instruct groupFirstInstruct = groupInstructList.get(0); + for (int i = 0; i < runningInstructList.size(); i++) { + if (runningInstructList.get(i).getInstructId().equals(groupFirstInstruct.getInstructId())) { + runningInstructIndex = i; + runningInstructRunCount = 1; + runInstruct(runningInstructList.get(runningInstructIndex)); + return; + } + } + } + } else { + //执行完成, 正常执行下一条指令 + runNextInstruct(); + } + } else { + //是指令 + //判断当前指令是否是当前组的最后一条 + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + Instruct lastRunInstructGroup = instructService.getById(lastEntry.getKey()); + List groupInstructList = getGroupInstructList(lastRunInstructGroup); + if (currentInstruct.getInstructId().equals(groupInstructList.get(groupInstructList.size() - 1).getInstructId())) { + //是当前组最后一条指令 + //判断当前组执行次数 + if (lastEntry.getValue() < lastRunInstructGroup.getRepeatCount()) { + updateGroupRunCount(lastEntry.getKey(), lastEntry.getValue() + 1); + Instruct groupFirstInstruct = groupInstructList.get(0); + for (int i = 0; i < runningInstructList.size(); i++) { + if (runningInstructList.get(i).getInstructId().equals(groupFirstInstruct.getInstructId())) { + runningInstructIndex = i; + runningInstructRunCount = 1; + runInstruct(runningInstructList.get(runningInstructIndex)); + return; + } + } + } else { + runningInstructGroupMap.remove(lastEntry.getKey()); + runNextInstruct(); + } + } else { + runningInstructIndex++; + runningInstructRunCount = 1; + runInstruct(runningInstructList.get(runningInstructIndex)); + } + } + } + + /** + * 检查指令组执行次数 + * + * @return 是否继续执行 + */ + private boolean checkGroupRunCount() { + Map.Entry lastEntry = getRunningInstructGroupMapLast(); + Instruct lastGroup = instructService.getById(lastEntry.getKey()); + if (lastEntry.getValue() < lastGroup.getRepeatCount()) { + //还要执行, 将运行次数加1 + updateGroupRunCount(lastEntry.getKey(), lastEntry.getValue() + 1); + return true; + } else { + //执行完成 + runningInstructGroupMap.remove(lastEntry.getKey()); + if (!runningInstructGroupMap.isEmpty()) { + return checkGroupRunCount(); + } else { + //整个组执行完了 + return false; + } + } + } + + /** + * 更新指令组运行次数 + * + * @param instructId 指令id + * @param runCount 运行次数 + */ + private void updateGroupRunCount(String instructId, int runCount) { + runningInstructGroupMap.replace(instructId, runCount); + } + + /** + * 获取运行中map的最后一个组 + */ + private Map.Entry getRunningInstructGroupMapLast() { + List> entries = new ArrayList<>(runningInstructGroupMap.entrySet()); + return entries.get(entries.size() - 1); + } + + /** + * 获取指令组内指令 + * + * @param instructGroup 指令组 + */ + private List getGroupInstructList(Instruct instructGroup) { + List instructList = new ArrayList<>(); + runningInstructList.forEach(instruct -> { + if (instruct.getParentEncoding().startsWith(instructGroup.getEncoding())) { + instructList.add(instruct); + } + }); + return instructList; + } +} diff --git a/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/SchemeRunThread.java b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/SchemeRunThread.java new file mode 100644 index 0000000..f6d0134 --- /dev/null +++ b/src/main/java/com/cbsd/universaltestsoftware_client/websocket/command/SchemeRunThread.java @@ -0,0 +1,218 @@ +package com.cbsd.universaltestsoftware_client.websocket.command; + +import com.alibaba.fastjson.JSON; +import com.cbsd.universaltestsoftware_client.entity.Instruct; +import com.cbsd.universaltestsoftware_client.entity.Invocation; +import com.cbsd.universaltestsoftware_client.service.impl.ChannelDataServiceImpl; +import com.cbsd.universaltestsoftware_client.service.impl.InstructServiceImpl; +import com.cbsd.universaltestsoftware_client.util.DateUtils; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class SchemeRunThread extends Thread implements InvocationRunThread.OnInvocationRunListener { + + public interface OnSchemeRunListener { + void onComplete(String schemeId); + } + + @Setter + private OnSchemeRunListener onSchemeRunListener; + + private final CommandClientMessage clientMessage; + + private final InstructServiceImpl instructService; + + private final ChannelDataServiceImpl channelDataService; + + /** + * 指令文件路径 + */ + private final String commandRootPath; + + private List invocationList; + + /** + * 用例执行线程map, key用例id, value用例执行线程 + */ + private final Map invocationRunThreadMap = new HashMap<>(); + + private int invocationRunIndex = 0; + + private int invocationCompleteCount = 0; + + public SchemeRunThread(CommandClientMessage clientMessage, InstructServiceImpl instructService, ChannelDataServiceImpl channelDataService, String commandRootPath) { + this.clientMessage = clientMessage; + this.instructService = instructService; + this.channelDataService = channelDataService; + this.commandRootPath = commandRootPath; + } + + @Override + public void run() { + log.info(" 遥控方案开始运行, 运行模式:{}, 时间: {}", clientMessage.getRunModelLabel(), DateUtils.dateToString()); + List invocationIdList = clientMessage.getInvocationIdList(); + //根据选择用例id列表获取用例列表 + invocationList = instructService.getInvocationListByIds(invocationIdList); + if (invocationList == null || invocationList.isEmpty()) { + schemeRunCompleted(); + return; + } + + if (clientMessage.getRunMode() == CommandClientMessage.RunMode.SEQUENTIAL.getValue() || clientMessage.getRunMode() == CommandClientMessage.RunMode.SINGLE.getValue()) { + //顺序运行 或 单步运行 + createAndStartInvocationRunThread(invocationList.get(invocationRunIndex)); + } else if (clientMessage.getRunMode() == CommandClientMessage.RunMode.PARALLEL.getValue()) { + //并行运行 + for (Invocation invocation : invocationList) { + createAndStartInvocationRunThread(invocation); + } + } + } + + private void createAndStartInvocationRunThread(Invocation invocation) { + InvocationRunThread invocationRunThread = new InvocationRunThread(invocation.getInvocationId(), clientMessage, instructService, channelDataService, commandRootPath); + invocationRunThread.setOnInvocationRunListener(this); + invocationRunThreadMap.put(invocation.getInvocationId(), invocationRunThread); + invocationRunThread.start(); + log.info(" 用例 {} 开始运行, 时间: {}", invocation.getInvocationName(), DateUtils.dateToString()); + } + + @Override + public void onInvocationComplete(String invocationId) { + Invocation currentRunningInvocation = invocationList.get(invocationRunIndex); + log.info(" 用例 {} 运行完成, 时间: {}", currentRunningInvocation.getInvocationName(), DateUtils.dateToString()); + invocationRunThreadMap.remove(invocationId); + if (clientMessage.getRunMode() == CommandClientMessage.RunMode.SEQUENTIAL.getValue() || clientMessage.getRunMode() == CommandClientMessage.RunMode.SINGLE.getValue()) { + //顺序运行 或 单步运行 + if (invocationRunIndex < invocationList.size() - 1) { + invocationRunIndex++; + createAndStartInvocationRunThread(invocationList.get(invocationRunIndex)); + } else { + //运行完成 + schemeRunCompleted(); + } + } else if (clientMessage.getRunMode() == CommandClientMessage.RunMode.PARALLEL.getValue()) { + //并行运行 + invocationCompleteCount++; + if (invocationList != null && invocationCompleteCount == invocationList.size()) { + schemeRunCompleted(); + } + } + } + + @Override + public void onInvocationRunning(CommandRunMessage runMessage) { + instructService.broadcast(JSON.toJSONString(runMessage)); + } + + @Override + public void onInvocationDebug(String invocationId) { + CommandRunMessage runMessage = new CommandRunMessage(); + runMessage.setRunStatus(CommandRunMessage.RunStatus.DEBUG.getValue()); + runMessage.setRunMode(clientMessage.getRunMode()); + runMessage.setSchemeId(clientMessage.getSchemeId()); + runMessage.setInvocationId(invocationId); + runMessage.setCommandStatus(2); + instructService.broadcast(JSON.toJSONString(runMessage)); + } + + /** + * 方案运行完成 + */ + private void schemeRunCompleted() { + log.info(" 遥控方案运行完成, 时间: {}", DateUtils.dateToString()); + CommandRunMessage runMessage = new CommandRunMessage(); + runMessage.setRunStatus(CommandRunMessage.RunStatus.COMPLETE.getValue()); + runMessage.setSchemeId(clientMessage.getSchemeId()); + instructService.broadcast(JSON.toJSONString(runMessage)); + if (onSchemeRunListener != null) { + onSchemeRunListener.onComplete(clientMessage.getSchemeId()); + } + stopRun(); + } + + /** + * 暂停运行 + */ + public void pauseRun() { + for (InvocationRunThread invocationRunThread : invocationRunThreadMap.values()) { + invocationRunThread.pauseRun(); + } + } + + /** + * 继续运行 + */ + public void resumeRun(String invocationId) { + if (clientMessage.getRunMode() == CommandClientMessage.RunMode.PARALLEL.getValue()){ + InvocationRunThread invocationRunThread = invocationRunThreadMap.get(invocationId); + if (invocationRunThread != null){ + invocationRunThread.resumeRun(); + } + } else { + for (InvocationRunThread invocationRunThread : invocationRunThreadMap.values()) { + invocationRunThread.resumeRun(); + } + } + } + + /** + * 停止运行 + */ + public void stopRun() { + for (InvocationRunThread invocationRunThread : invocationRunThreadMap.values()) { + invocationRunThread.stopRun(); + invocationRunThread.interrupt(); + } + invocationRunThreadMap.clear(); + interrupt(); + } + + /** + * 重新运行 + */ + public void restartRun() { + for (InvocationRunThread invocationRunThread : invocationRunThreadMap.values()) { + invocationRunThread.restartRun(); + } + } + + /** + * 单步运行 + */ + public void singleStepRun() { + for (InvocationRunThread invocationRunThread : invocationRunThreadMap.values()) { + invocationRunThread.singleStepRun(); + } + } + + /** + * 断点单步运行 + */ + public void breakPointSingleStepRun(String invocationId) { + InvocationRunThread invocationRunThread = invocationRunThreadMap.get(invocationId); + if (invocationRunThread != null){ + invocationRunThread.breakPointSingleStepRun(); + } + } + + + /** + * 获取当前方案运行状态 + */ + public List getRunState() { + List runStateList = new ArrayList<>(); + for (InvocationRunThread invocationRunThread : invocationRunThreadMap.values()) { + CommandRunMessage stateMsg = invocationRunThread.getRunState(); + stateMsg.setSchemeId(clientMessage.getSchemeId()); + runStateList.add(stateMsg); + } + return runStateList; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..b1f4fe8 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,75 @@ +server: + port: 8081 + servlet: + context-path: /universaltestsoftware_client_api + encoding: + force: true + charset: UTF-8 + enabled: true + tomcat: + uri-encoding: UTF-8 +logging: + level: + web: trace + org.springframework.web.socket: WARN +#数据源连接信息 +spring: + servlet: + multipart: + enabled: true + max-file-size: 300MB + max-request-size: 2048MB + datasource: + url: jdbc:mysql://192.168.0.201:3306/universaltestsoftware_client_api?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai&useSSL=false&connectTimeout=30000&socketTimeout=30000 + username: root + password: Fushoukeji123 + driver-class-name: com.mysql.jdbc.Driver + druid: + keep-alive: true # 开启连接保活(核心) + initial-size: 5 # 初始化连接数 + min-idle: 5 # 最小空闲连接数 + max-active: 2000 # 最大活跃连接数 + max-wait: 30000 # 获取连接最大等待时间(30秒) + # 调整空闲连接检测频率:60秒检测一次(降低性能开销) + time-between-eviction-runs-millis: 60000 + # 连接最小生存时间:30分钟(远大于MySQL默认超时,避免提前回收) + min-evictable-idle-time-millis: 1800000 + validation-query: SELECT 1 # 测试连接有效性的SQL + test-while-idle: true # 空闲时检测连接(必须开启) + test-on-borrow: true # 获取连接时检测(解决失效连接问题,核心修改) + test-on-return: false # 归还时不检测(性能优化) + pool-prepared-statements: false # MySQL建议关闭PSCache + max-pool-prepared-statement-per-connection-size: 20 + filters: stat,wall # 监控+SQL防火墙 + connection-properties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' + # Wall过滤器配置 + filter: + wall: + enabled: true + config: + noneBaseStatementAllow: true + commentAllow: true + mysql: + allowMultiQueries: true + flyway: + #是否启用 + enabled: true + # 可以支持多个location, 用','隔开 + locations: classpath:db/migration + #是否创建元数据表 + validate-on-migrate: true + # flyway 的 clean 命令会删除指定 schema 下的所有 table, 生产务必禁掉。这个默认值是 + clean-disabled: false + # 如果数据库不是空表,需要设置成 true,否则启动报错 + baseline-on-migrate: true + # 版本控制日志表,默认flyway_schema_history,不同系统建议修改数据 + # table: flyway_schema_history + +cbsd: + uploadfilesrootpath: "C:/Java" + scriptBasePackageName: "com.cbsd.client" + scriptRootPath: C:\script + commandRootPath: C:\command + downloadUrl : C:\download + sampling-interval-ms: 1000 # 字段解析采样间隔(毫秒),默认1000即每秒最多解析一帧字段 + diff --git a/src/main/resources/application-pro.yml b/src/main/resources/application-pro.yml new file mode 100644 index 0000000..01622bc --- /dev/null +++ b/src/main/resources/application-pro.yml @@ -0,0 +1,74 @@ +server: + port: 8081 + servlet: + context-path: /universaltestsoftware_client_api + encoding: + force: true + charset: UTF-8 + enabled: true + tomcat: + uri-encoding: UTF-8 +logging: + level: + web: trace + org.springframework.web.socket: WARN +#数据源连接信息 +spring: + servlet: + multipart: + enabled: true + max-file-size: 300MB + max-request-size: 2048MB + datasource: + url: jdbc:mysql://127.0.0.1:3306/universaltestsoftware_client_api?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai&useSSL=false&connectTimeout=30000&socketTimeout=30000 + username: root + password: + driver-class-name: com.mysql.jdbc.Driver + druid: + keep-alive: true # 开启连接保活(核心) + initial-size: 5 # 初始化连接数 + min-idle: 5 # 最小空闲连接数 + max-active: 2000 # 最大活跃连接数 + max-wait: 30000 # 获取连接最大等待时间(30秒) + # 调整空闲连接检测频率:60秒检测一次(降低性能开销) + time-between-eviction-runs-millis: 60000 + # 连接最小生存时间:30分钟(远大于MySQL默认超时,避免提前回收) + min-evictable-idle-time-millis: 1800000 + validation-query: SELECT 1 # 测试连接有效性的SQL + test-while-idle: true # 空闲时检测连接(必须开启) + test-on-borrow: true # 获取连接时检测(解决失效连接问题,核心修改) + test-on-return: false # 归还时不检测(性能优化) + pool-prepared-statements: false # MySQL建议关闭PSCache + max-pool-prepared-statement-per-connection-size: 20 + filters: stat,wall # 监控+SQL防火墙 + connection-properties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' + # Wall过滤器配置 + filter: + wall: + enabled: true + config: + noneBaseStatementAllow: true + commentAllow: true + mysql: + allowMultiQueries: true + flyway: + #是否启用 + enabled: true + # 可以支持多个location, 用','隔开 + locations: classpath:db/migration + #是否创建元数据表 + validate-on-migrate: true + # flyway 的 clean 命令会删除指定 schema 下的所有 table, 生产务必禁掉。这个默认值是 + clean-disabled: false + # 如果数据库不是空表,需要设置成 true,否则启动报错 + baseline-on-migrate: true + # 版本控制日志表,默认flyway_schema_history,不同系统建议修改数据 + # table: flyway_schema_history + +cbsd: + uploadfilesrootpath: "C:/Java" + scriptBasePackageName: "com.cbsd.client" + scriptRootPath: C:\script + commandRootPath: C:\command + downloadUrl : C:\download + sampling-interval-ms: 1000 # 字段解析采样间隔(毫秒),默认1000即每秒最多解析一帧字段 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..92a118b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,23 @@ +spring: + #放开springmvc对静态资源的拦截 + mvc: + static-path-pattern: /static/** + profiles: + active: pro + +mybatis-plus: + # 包扫描路径(当前项目的实体类所在位置。别名包扫描路径,通过该属性可以给包中的类注册别名,多个路径用逗号分割) + type-aliases-package: com.cbsd.managerUser.* + # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置) + mapper-locations: classpath*:mapper/**/*.xml + configuration: + # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射 + map-underscore-to-camel-case: true + # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段 + # 允许在resultType="map"时映射null值 + call-setters-on-nulls: true + # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用(包括执行结果) +# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +springfox: + documentation: + auto-startup: true #false关闭swagger文档 true打开swagger diff --git a/src/main/resources/db/migration/V1_0__init_client.sql b/src/main/resources/db/migration/V1_0__init_client.sql new file mode 100644 index 0000000..6537788 --- /dev/null +++ b/src/main/resources/db/migration/V1_0__init_client.sql @@ -0,0 +1,236 @@ +/* + Navicat Premium Dump SQL + + Source Server : cbsd-dev + Source Server Type : MySQL + Source Server Version : 50638 (5.6.38) + Source Host : cloud.cbsd-pro.com:3306 + Source Schema : universaltestsoftware_client_api + + Target Server Type : MySQL + Target Server Version : 50638 (5.6.38) + File Encoding : 65001 + + Date: 14/10/2025 14:07:01 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for boot_log +-- ---------------------------- +DROP TABLE IF EXISTS `boot_log`; +CREATE TABLE `boot_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID,自增长', + `event_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '事件ID,用于唯一标识一个日志事件', + `event_date` datetime NULL DEFAULT NULL COMMENT '事件发生时间', + `thread` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产生日志的线程名称', + `log_class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '产生日志的类名', + `message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志消息内容', + `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '异常堆栈信息', + `level` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志级别(如:DEBUG, INFO, WARN, ERROR)', + `time` datetime NULL DEFAULT NULL COMMENT '日志记录时间', + `method` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '产生日志的方法/函数名', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 642869 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统启动日志表,记录应用程序运行过程中的日志信息' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for channel_data +-- ---------------------------- +DROP TABLE IF EXISTS `channel_data`; +CREATE TABLE `channel_data` ( + `data_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '解析数据id', + `channel_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通道id', + `data` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '数据内容', + `protocol_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '协议id', + `protocol_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '协议内容', + `time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据时间', + PRIMARY KEY (`data_id`) USING BTREE, + INDEX `INDEX_CHANNEL`(`channel_id`, `protocol_id`) USING BTREE COMMENT '通道id,协议id' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '解析的数据' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for data_board +-- ---------------------------- +DROP TABLE IF EXISTS `data_board`; +CREATE TABLE `data_board` ( + `data_board_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据看板id', + `data_board_type` int(1) NULL DEFAULT NULL COMMENT '看板类型,0:表格,1:曲线', + `data_board_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '看板名字', + `data_type` int(1) NULL DEFAULT NULL COMMENT '0:遥测,1:数据回放', + `create_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`data_board_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据看板' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for data_board_channel +-- ---------------------------- +DROP TABLE IF EXISTS `data_board_channel`; +CREATE TABLE `data_board_channel` ( + `data_board_channel_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据看板通道id', + `data_board_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据看板id', + `channel_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通道Id', + `channel_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通道名字', + `parameter` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '参数名字', + `create_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `sn` int(11) NULL DEFAULT NULL COMMENT '索引', + `is_left_yaxis` int(1) NULL DEFAULT 1 COMMENT '是否是左边的y轴 0否 1是 默认1', + `legend_color` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图例颜色', + PRIMARY KEY (`data_board_channel_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '数据看板通道' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for instruct +-- ---------------------------- +DROP TABLE IF EXISTS `instruct`; +CREATE TABLE `instruct` ( + `instruct_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '指令id', + `instruct_type` int(11) NOT NULL DEFAULT 1 COMMENT '指令类型 1.常规指令 2.指令组', + `encoding` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编码', + `parent_encoding` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级编码', + `parent_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级名字', + `instruct_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '指令名字', + `server_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '服务id', + `ip_address` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ip地址', + `tcp_port` int(12) NULL DEFAULT NULL COMMENT '服务端tcp端口', + `instruct_text` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '指令数据', + `channel_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通道id', + `channel_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通道名字', + `invocation_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '调用用列id', + `operation_type` int(11) NOT NULL DEFAULT 0 COMMENT '指令类型 0.数据 1.寄存器', + `extra_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '额外数据', + `board_type_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '板卡类型名称', + `board_index` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '板卡编号', + `protocol_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '协议id', + `protocol_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '协议名字', + `protocol_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '协议内容', + `sort` int(12) NULL DEFAULT NULL COMMENT '排序', + `status` int(1) NULL DEFAULT 1 COMMENT '状态0:禁用,1:启用', + `lead_time` int(32) NOT NULL DEFAULT 0 COMMENT '前置等待时间ms', + `tail_time` int(32) NOT NULL DEFAULT 0 COMMENT '后置等待时间ms', + `hit_count` int(32) NULL DEFAULT 0 COMMENT '命中次数', + `last_hit_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最后命中时间', + `instruct_tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '指令标识', + `pre_process_script` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '预处理脚本', + `pre_process_script_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '预处理脚本文件名称', + `pre_process_script_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '预处理脚本参数', + `pre_process_script_file` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '预处理脚本文件', + `pre_process_script_comment` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '预处理脚本注释', + `repeat_count` int(32) NULL DEFAULT 1 COMMENT '重复次数', + `branch_condition_script` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '分支条件脚本', + `branch_condition_script_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分支条件脚本名称', + `branch_condition_script_file` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分支条件脚本文件', + `branch_condition_script_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '分支条件脚本参数', + `branch_condition_script_comment` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '分支条件脚本注释', + `check_script` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '检查脚本', + `check_script_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '检查脚本名称', + `check_script_file` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '检查脚本文件', + `check_script_comment` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '检查脚本注释', + `instruct_data_file` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '指令数据文件', + `jump_target` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '跳转目标', + `break_point` int(11) NOT NULL DEFAULT 0 COMMENT '断点 1.是 0.否', + PRIMARY KEY (`instruct_id`) USING BTREE, + UNIQUE INDEX `UNIQUE_NAME`(`invocation_id`, `instruct_name`) USING BTREE COMMENT '同一用例指令名称不能重复' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '指令' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for invocation +-- ---------------------------- +DROP TABLE IF EXISTS `invocation`; +CREATE TABLE `invocation` ( + `invocation_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用例id', + `invocation_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用例名字', + `sort` int(11) NOT NULL DEFAULT 1 COMMENT '用例序号', + `scheme_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方案id', + `create_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `update_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`invocation_id`) USING BTREE, + UNIQUE INDEX `UNIQUEEN_NAME`(`scheme_id`, `invocation_name`) USING BTREE COMMENT '同一方案用例名称唯一' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用列' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for parser +-- ---------------------------- +DROP TABLE IF EXISTS `parser`; +CREATE TABLE `parser` ( + `parser_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '解析器id', + `parser_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '解析器名称', + `parser_sort` int(11) NULL DEFAULT NULL COMMENT '解析器顺序', + `channel_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通道id', + `is_delete` int(11) NOT NULL DEFAULT 0 COMMENT '是否删除 1.是 0.否', + `create_time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`parser_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '解析器' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for parser_protocol +-- ---------------------------- +DROP TABLE IF EXISTS `parser_protocol`; +CREATE TABLE `parser_protocol` ( + `parser_protocol_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '解析器协议id', + `parser_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '解析器id', + `protocol_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '协议id', + `create_time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`parser_protocol_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '解析器协议' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for protocol +-- ---------------------------- +DROP TABLE IF EXISTS `protocol`; +CREATE TABLE `protocol` ( + `protocol_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '协议id', + `protocol_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '协议名字', + `protocol_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '协议内容', + `protocol_type` int(11) NULL DEFAULT NULL COMMENT '协议类型 1.解析 2.指令', + `protocol_tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '协议标签', + `create_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `update_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`protocol_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '协议' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for scheme +-- ---------------------------- +DROP TABLE IF EXISTS `scheme`; +CREATE TABLE `scheme` ( + `scheme_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '方案id', + `scheme_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方案名字', + `create_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `update_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`scheme_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '方案' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for server +-- ---------------------------- +DROP TABLE IF EXISTS `server`; +CREATE TABLE `server` ( + `server_id` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '服务端id', + `server_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '服务端名字', + `ip_address` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ip地址', + `tcp_port` int(12) NULL DEFAULT NULL COMMENT '服务端tcp端口', + `http_port` int(12) NULL DEFAULT NULL COMMENT '服务端http端口', + `reconnect_initial_delay` int(32) NULL DEFAULT 1000 COMMENT '重连初始延迟', + `reconnect_max_delay` int(32) NULL DEFAULT 10000 COMMENT '重连最大延迟', + `reconnect_multiplier` double(12, 2) NULL DEFAULT 1.50 COMMENT '重连延迟乘数', + `create_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `update_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改时间', + `is_delete` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '是否删除0否1是', + `connect_status` int(1) NULL DEFAULT 0 COMMENT '连接状态0:不连接,1要连接', + PRIMARY KEY (`server_id`) USING BTREE, + UNIQUE INDEX `name_index`(`server_name`, `is_delete`) USING BTREE COMMENT '服务端名字不能重复' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '服务端' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for system_config +-- ---------------------------- +DROP TABLE IF EXISTS `system_config`; +CREATE TABLE `system_config` ( + `type` int(11) NOT NULL COMMENT '类型: 1.指令显示列', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '内容', + PRIMARY KEY (`type`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统配置(仅类型和内容)' ROW_FORMAT = Compact; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/src/main/resources/db/migration/V1_10__add_channel_data_raw.sql b/src/main/resources/db/migration/V1_10__add_channel_data_raw.sql new file mode 100644 index 0000000..d82fd8e --- /dev/null +++ b/src/main/resources/db/migration/V1_10__add_channel_data_raw.sql @@ -0,0 +1 @@ +ALTER TABLE `channel_data` ADD COLUMN `raw_data` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '原始帧数据(十六进制)' AFTER `data`; diff --git a/src/main/resources/db/migration/V1_1__sensor_equipment.sql b/src/main/resources/db/migration/V1_1__sensor_equipment.sql new file mode 100644 index 0000000..6d2b0fc --- /dev/null +++ b/src/main/resources/db/migration/V1_1__sensor_equipment.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS `sensor_equipment`; +CREATE TABLE `sensor_equipment` ( + `equipment_id` varchar(32) NOT NULL COMMENT '设备id', + `equipment_name` varchar(255) DEFAULT NULL COMMENT '设备名称', + `equipment_desc` text COMMENT '设备描述', + `channel_id` varchar(32) DEFAULT NULL COMMENT '通道id', + `channel_address` varchar(255) DEFAULT NULL COMMENT '通道地址', + `equipment_param` varchar(255) DEFAULT NULL COMMENT '设备参数', + `create_time` varchar(20) DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`equipment_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='传感设备'; \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_2__instruct_change.sql b/src/main/resources/db/migration/V1_2__instruct_change.sql new file mode 100644 index 0000000..e400862 --- /dev/null +++ b/src/main/resources/db/migration/V1_2__instruct_change.sql @@ -0,0 +1,4 @@ +ALTER TABLE instruct ADD COLUMN instruct_function_name varchar(255) DEFAULT NULL COMMENT '指令寄存器方法名称'; + +ALTER TABLE instruct DROP COLUMN protocol_content; + diff --git a/src/main/resources/db/migration/V1_3__instruct_data_split.sql b/src/main/resources/db/migration/V1_3__instruct_data_split.sql new file mode 100644 index 0000000..16af3fb --- /dev/null +++ b/src/main/resources/db/migration/V1_3__instruct_data_split.sql @@ -0,0 +1,2 @@ +ALTER TABLE instruct ADD COLUMN data_splitting_length int(11) NOT NULL DEFAULT '0' COMMENT '数据拆分长度'; + diff --git a/src/main/resources/db/migration/V1_4__instruct_tail_time.sql b/src/main/resources/db/migration/V1_4__instruct_tail_time.sql new file mode 100644 index 0000000..fa293d2 --- /dev/null +++ b/src/main/resources/db/migration/V1_4__instruct_tail_time.sql @@ -0,0 +1 @@ +ALTER TABLE instruct ALTER COLUMN tail_time SET DEFAULT '1000'; \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_5__data_board_channel.sql b/src/main/resources/db/migration/V1_5__data_board_channel.sql new file mode 100644 index 0000000..5f612b6 --- /dev/null +++ b/src/main/resources/db/migration/V1_5__data_board_channel.sql @@ -0,0 +1,3 @@ +ALTER TABLE `data_board_channel` +ADD COLUMN `protocol_id` varchar(64) DEFAULT NULL COMMENT '协议id' AFTER `legend_color`, +ADD COLUMN `protocol_name` varchar(255) DEFAULT NULL COMMENT '协议名字' AFTER `protocol_id`; \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_6__channel_data_statistics.sql b/src/main/resources/db/migration/V1_6__channel_data_statistics.sql new file mode 100644 index 0000000..7c9554d --- /dev/null +++ b/src/main/resources/db/migration/V1_6__channel_data_statistics.sql @@ -0,0 +1,20 @@ +DROP TABLE IF EXISTS `channel_data_traffic`; +CREATE TABLE `channel_data_traffic` ( + `data_traffic_id` varchar(32) NOT NULL COMMENT '数据流量id', + `channel_id` varchar(50) DEFAULT NULL COMMENT '通道id', + `byte_count` int(11) NOT NULL DEFAULT '0' COMMENT '字节数', + `update_time` varchar(50) DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`data_traffic_id`), + UNIQUE KEY `INDEX_CHANNEL_ID` (`channel_id`) USING BTREE COMMENT '通道唯一' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通道流量统计'; + +DROP TABLE IF EXISTS `channel_data_hit`; +CREATE TABLE `channel_data_hit` ( + `hit_id` varchar(32) NOT NULL COMMENT '命中id', + `channel_id` varchar(50) DEFAULT NULL COMMENT '通道id', + `protocol_id` varchar(50) DEFAULT NULL COMMENT '协议id', + `hit_count` int(11) NOT NULL DEFAULT '0' COMMENT '命中次数', + `update_time` varchar(50) DEFAULT NULL COMMENT '命中更新时间', + PRIMARY KEY (`hit_id`), + UNIQUE KEY `INDEX_1` (`channel_id`,`protocol_id`) USING BTREE COMMENT '通道+协议唯一' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通道协议命中次数'; \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_7__1_27.sql b/src/main/resources/db/migration/V1_7__1_27.sql new file mode 100644 index 0000000..76f84f6 --- /dev/null +++ b/src/main/resources/db/migration/V1_7__1_27.sql @@ -0,0 +1,3 @@ +ALTER TABLE instruct ADD COLUMN instruct_text_data longtext DEFAULT NULL COMMENT '指令数据列表'; + + diff --git a/src/main/resources/db/migration/V1_8__download.sql b/src/main/resources/db/migration/V1_8__download.sql new file mode 100644 index 0000000..a510806 --- /dev/null +++ b/src/main/resources/db/migration/V1_8__download.sql @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS `download`; +CREATE TABLE `download` ( + `download_id` varchar(64) NOT NULL COMMENT '下载id', + `start_time` varchar(64) DEFAULT NULL COMMENT '开始时间', + `end_time` varchar(64) DEFAULT NULL COMMENT '结束时间', + `create_time` varchar(64) DEFAULT NULL COMMENT '创建时间', + `channel_name` varchar(255) DEFAULT NULL COMMENT '通道名字', + `protocol_name` varchar(255) DEFAULT NULL COMMENT '协议名字', + `state` int(1) DEFAULT '1' COMMENT '1:生成中,2:已完成', + `download_url` varchar(255) DEFAULT NULL COMMENT '下载地址', + `type` int(1) DEFAULT NULL COMMENT '类型 1:解析数据 ,2:原数据', + PRIMARY KEY (`download_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/src/main/resources/db/migration/V1_9__2_10.sql b/src/main/resources/db/migration/V1_9__2_10.sql new file mode 100644 index 0000000..a18184e --- /dev/null +++ b/src/main/resources/db/migration/V1_9__2_10.sql @@ -0,0 +1,3 @@ +CREATE INDEX idx_channel_protocol_time ON channel_data (channel_id, protocol_id, time DESC); + + diff --git a/src/main/resources/mapper/BootLogMapper.xml b/src/main/resources/mapper/BootLogMapper.xml new file mode 100644 index 0000000..c023821 --- /dev/null +++ b/src/main/resources/mapper/BootLogMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/mapper/ChannelDataHitMapper.xml b/src/main/resources/mapper/ChannelDataHitMapper.xml new file mode 100644 index 0000000..25525b4 --- /dev/null +++ b/src/main/resources/mapper/ChannelDataHitMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + hit_id,channel_id,protocol_id,hit_count,update_time + + diff --git a/src/main/resources/mapper/ChannelDataMapper.xml b/src/main/resources/mapper/ChannelDataMapper.xml new file mode 100644 index 0000000..b772ed2 --- /dev/null +++ b/src/main/resources/mapper/ChannelDataMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + data_id,channel_id,data,time + + diff --git a/src/main/resources/mapper/ChannelDataTrafficMapper.xml b/src/main/resources/mapper/ChannelDataTrafficMapper.xml new file mode 100644 index 0000000..8aef8f8 --- /dev/null +++ b/src/main/resources/mapper/ChannelDataTrafficMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + data_traffic_id,channel_id,byte_count,update_time + + diff --git a/src/main/resources/mapper/DataBoardChannelMapper.xml b/src/main/resources/mapper/DataBoardChannelMapper.xml new file mode 100644 index 0000000..33b190c --- /dev/null +++ b/src/main/resources/mapper/DataBoardChannelMapper.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/src/main/resources/mapper/DataBoardMapper.xml b/src/main/resources/mapper/DataBoardMapper.xml new file mode 100644 index 0000000..04b6296 --- /dev/null +++ b/src/main/resources/mapper/DataBoardMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + data_board_id, data_board_type, data_board_name, create_time + + + diff --git a/src/main/resources/mapper/DownloadMapper.xml b/src/main/resources/mapper/DownloadMapper.xml new file mode 100644 index 0000000..1ad5874 --- /dev/null +++ b/src/main/resources/mapper/DownloadMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + download_id, start_time, end_time, create_time, channel_name, protocol_name, state, download_url + + + diff --git a/src/main/resources/mapper/InstructMapper.xml b/src/main/resources/mapper/InstructMapper.xml new file mode 100644 index 0000000..215ad4f --- /dev/null +++ b/src/main/resources/mapper/InstructMapper.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + UPDATE instruct + SET sort = #{sort} + WHERE instruct_id = #{instructId} + + + + + + UPDATE instruct t + JOIN ( + SELECT instruct_id, + (@rn := @rn + 1) AS rn + FROM (SELECT * FROM instruct WHERE invocation_id = #{invocationId} ORDER BY sort) tmp, + (SELECT @rn := 0) init + ) r ON t.instruct_id = r.instruct_id + SET t.sort = r.rn + WHERE t.invocation_id = #{invocationId} + + + + + + + + + UPDATE instruct + SET sort = 0 + WHERE instruct_id IN + + #{id} + + + + + + + + + + UPDATE instruct + SET sort = sort + 1 + WHERE invocation_id = #{invocationId} + AND sort >= #{sort} and instruct_id !=#{instructId} + + + UPDATE instruct + SET sort = sort - 1 + WHERE invocation_id = #{invocationId} + AND sort >= #{oldSort} + AND sort <= #{newSort} + AND instruct_id != #{instructId} + + + UPDATE instruct + SET sort = sort + 1 + WHERE invocation_id = #{invocationId} + AND sort >= #{newSort} + AND sort <= #{oldSort} + AND instruct_id != #{instructId} + + + + diff --git a/src/main/resources/mapper/InvocationMapper.xml b/src/main/resources/mapper/InvocationMapper.xml new file mode 100644 index 0000000..529965d --- /dev/null +++ b/src/main/resources/mapper/InvocationMapper.xml @@ -0,0 +1,17 @@ + + + + + + update invocation set sort = sort - 1 where sort > #{oldSort} and sort <= #{newSort} and scheme_id = #{schemeId} + + + + update invocation set sort = sort + 1 where sort >= #{newSort} and sort < #{oldSort} and scheme_id = #{schemeId} + + + + update invocation set sort = sort - 1 where sort >= #{startSort} and scheme_id = #{schemeId} + + + diff --git a/src/main/resources/mapper/ParserMapper.xml b/src/main/resources/mapper/ParserMapper.xml new file mode 100644 index 0000000..78dc214 --- /dev/null +++ b/src/main/resources/mapper/ParserMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + parser_id,parser_name,parser_sort,channel_id,is_delete,create_time + + diff --git a/src/main/resources/mapper/ParserProtocolMapper.xml b/src/main/resources/mapper/ParserProtocolMapper.xml new file mode 100644 index 0000000..eaeeda4 --- /dev/null +++ b/src/main/resources/mapper/ParserProtocolMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + parser_protocol_id,parser_id,protocol_id,create_time + + diff --git a/src/main/resources/mapper/ProtocolMapper.xml b/src/main/resources/mapper/ProtocolMapper.xml new file mode 100644 index 0000000..071a5a0 --- /dev/null +++ b/src/main/resources/mapper/ProtocolMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + protocol_id, protocol_name, protocol_content, protocol_tag, create_time, update_time + + + diff --git a/src/main/resources/mapper/SchemeMapper.xml b/src/main/resources/mapper/SchemeMapper.xml new file mode 100644 index 0000000..548cafc --- /dev/null +++ b/src/main/resources/mapper/SchemeMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + scheme_id, scheme_name, create_time, update_time + + + diff --git a/src/main/resources/mapper/SensorEquipmentMapper.xml b/src/main/resources/mapper/SensorEquipmentMapper.xml new file mode 100644 index 0000000..2fdbfd6 --- /dev/null +++ b/src/main/resources/mapper/SensorEquipmentMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/src/main/resources/mapper/ServerMapper.xml b/src/main/resources/mapper/ServerMapper.xml new file mode 100644 index 0000000..e43d5be --- /dev/null +++ b/src/main/resources/mapper/ServerMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/mapper/SystemConfigMapper.xml b/src/main/resources/mapper/SystemConfigMapper.xml new file mode 100644 index 0000000..fbb72f6 --- /dev/null +++ b/src/main/resources/mapper/SystemConfigMapper.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + type,content + + diff --git a/src/main/xml/protocol.xml b/src/main/xml/protocol.xml new file mode 100644 index 0000000..1e22ad7 --- /dev/null +++ b/src/main/xml/protocol.xml @@ -0,0 +1,163 @@ + + 温湿度协议 + + 0x01 + + 0 + 4 + CrcCheck + + + + + 0 + + 帧头 + hex + + 8 + 0 + + + + BIG + HEAD + + + + + + + + + + + + + + 1 + + 功能码 + hex + + 8 + 0 + + + + BIG + FIX + + + + + + + + + + + + + + 2 + + 数据长度 + int + + 8 + 0 + + + + BIG + LENGTH + + + + + + + + + + + + + + 3 + + 温度 + double + + 16 + 0 + + + TemperatureParser + BIG + VAR + + + + + + + + + + + + + + 4 + + 湿度 + double + + 16 + 0 + + + TemperatureParser + BIG + VAR + + + + + % + + + + + + + + + 5 + + 校验位 + hex + + 16 + 0 + + + + BIG + CALC + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/xml/protocolForInstruct.xml b/src/main/xml/protocolForInstruct.xml new file mode 100644 index 0000000..a6a5d02 --- /dev/null +++ b/src/main/xml/protocolForInstruct.xml @@ -0,0 +1,137 @@ + + 指令协议 + + + + + + + + + + 0 + + 地址 + hex + + 8 + 0 + + + + BIG + VAR + + + + + + + + + + + + + + 1 + + 功能码 + hex + + 8 + 0 + + + + BIG + VAR + + + + + + + + + + + + + + 2 + + 日期 + hex + + 8 + 0 + + + + BIG + DATE + + + + + + + + + + + + + + 2 + + 时间 + hex + + 8 + 0 + + + + BIG + TIME + + + + + + + + + + + + + + 3 + + 次数 + int + + 8 + 0 + + + + BIG + VAR + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/xml/protocolForVoltage.xml b/src/main/xml/protocolForVoltage.xml new file mode 100644 index 0000000..14ba483 --- /dev/null +++ b/src/main/xml/protocolForVoltage.xml @@ -0,0 +1,812 @@ + + 电压协议 + + + + + + + + + + 0 + + 电压-0 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 1 + + 电压-1 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 2 + + 电压-2 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 3 + + 电压-3 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 4 + + 电压-4 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 5 + + 电压-5 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 6 + + 电压-6 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 7 + + 电压-7 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 8 + + 电压-8 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 9 + + 电压-9 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 10 + + 电压-10 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 11 + + 电压-11 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 12 + + 电压-12 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 13 + + 电压-13 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 14 + + 电压-14 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 15 + + 电压-15 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 16 + + 电压-16 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 17 + + 电压-17 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 18 + + 电压-18 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 19 + + 电压-19 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 20 + + 电压-20 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 21 + + 电压-21 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 22 + + 电压-22 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 23 + + 电压-23 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 24 + + 电压-24 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 25 + + 电压-25 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 26 + + 电压-26 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 27 + + 电压-27 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 28 + + 电压-28 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 29 + + 电压-29 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 30 + + 电压-30 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + 31 + + 电压-31 + double + + 64 + 0 + + + VoltageParser + + VAR + + + + + V + + + + + + + + + \ No newline at end of file diff --git a/src/main/xml/protocolWithNoHeader.xml b/src/main/xml/protocolWithNoHeader.xml new file mode 100644 index 0000000..a1b3b4d --- /dev/null +++ b/src/main/xml/protocolWithNoHeader.xml @@ -0,0 +1,62 @@ + + 温湿度无帧头协议 + + + + + + + + + + 0 + + 温度 + double + + 16 + 0 + + + TemperatureParser + BIG + VAR + + + + + + + + + + + + + + 1 + + 湿度 + double + + 16 + 0 + + + TemperatureParser + BIG + VAR + + + + + % + + + + + + + + + \ No newline at end of file diff --git a/src/main/xml/protocol_digital.xml b/src/main/xml/protocol_digital.xml new file mode 100644 index 0000000..c5201de --- /dev/null +++ b/src/main/xml/protocol_digital.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/xml/protocol_variable_length.xml b/src/main/xml/protocol_variable_length.xml new file mode 100644 index 0000000..231fcfc --- /dev/null +++ b/src/main/xml/protocol_variable_length.xml @@ -0,0 +1,147 @@ + + 温湿度协议 + + 0x01 + + 0 + 7 + CrcCheck + + + 0x01 + 10 + + + 0x02 + 20 + + + + + + 0 + + 帧头 + hex + + 8 + 0 + + + + BIG + HEAD + + + + + + + + + + + + + + 1 + + 功能码 + hex + + 8 + 0 + + + + BIG + FIX + + + + + + + + + + + + + + 2 + + 数据长度 + int + + 8 + 0 + + + + BIG + VARIABLE_LENGTH + + + + + + + + + + + + + + 3 + + 温度 + double + + 0 + 0 + + + TemperatureParser + BIG + VAR + + + + + + + + + + + + + + 4 + + 校验位 + hex + + 16 + 0 + + + + BIG + CALC + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/cbsd/universaltestsoftware_client/UniversaltestsoftwareClientApiApplicationTests.java b/src/test/java/com/cbsd/universaltestsoftware_client/UniversaltestsoftwareClientApiApplicationTests.java new file mode 100644 index 0000000..f942a36 --- /dev/null +++ b/src/test/java/com/cbsd/universaltestsoftware_client/UniversaltestsoftwareClientApiApplicationTests.java @@ -0,0 +1,13 @@ +package com.cbsd.universaltestsoftware_client; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class UniversaltestsoftwareClientApiApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/cbsd/universaltestsoftware_client/controller/BoardControllerTest.java b/src/test/java/com/cbsd/universaltestsoftware_client/controller/BoardControllerTest.java new file mode 100644 index 0000000..4931612 --- /dev/null +++ b/src/test/java/com/cbsd/universaltestsoftware_client/controller/BoardControllerTest.java @@ -0,0 +1,119 @@ +package com.cbsd.universaltestsoftware_client.controller; + +import com.cbsd.universaltestsoftware_client.dto.BoardTemperatureDto; +import com.cbsd.universaltestsoftware_client.dto.ChannelPageDto; +import com.cbsd.universaltestsoftware_client.dto.PageBoard; +import com.cbsd.universaltestsoftware_client.entity.Board; +import com.cbsd.universaltestsoftware_client.entity.Channel; +import com.cbsd.universaltestsoftware_client.service.BoardService; +import com.cbsd.universaltestsoftware_client.util.Result; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.*; + +class BoardControllerTest { + @Mock + BoardService boardService; + @InjectMocks + BoardController boardController; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testPageList() { + when(boardService.pageList(any(PageBoard.class))).thenReturn("pageListResponse"); + + String result = boardController.pageList(new PageBoard("1960241264519610369")); + Assertions.assertEquals("pageListResponse", result); + } + + @Test + void testSaveOrUpdateBoard() { + when(boardService.saveOrUpdateBoard(any(Board.class))).thenReturn("saveOrUpdateBoardResponse"); + + String result = boardController.saveOrUpdateBoard(new Board()); + Assertions.assertEquals("saveOrUpdateBoardResponse", result); + } + + @Test + void testDeleteById() { + when(boardService.deleteById(anyString(), anyString())).thenReturn("deleteByIdResponse"); + + String result = boardController.deleteById("serverId", "boardId"); + Assertions.assertEquals("deleteByIdResponse", result); + } + + @Test + void testGetBoardType() { + when(boardService.getBoardType(anyString())).thenReturn("getBoardTypeResponse"); + + String result = boardController.getBoardType("serverId"); + Assertions.assertEquals("getBoardTypeResponse", result); + } + + @Test + void testSaveOrUpdateChannel() { + when(boardService.saveOrUpdateChannel(any(Channel.class))).thenReturn("saveOrUpdateChannelResponse"); + + String result = boardController.saveOrUpdateChannel(new Channel()); + Assertions.assertEquals("saveOrUpdateChannelResponse", result); + } + + @Test + void testPageChannelList() { + when(boardService.pageChannelList(any(ChannelPageDto.class))).thenReturn(new Result()); + + Result result = boardController.pageChannelList(new ChannelPageDto("", "")); + Assertions.assertEquals(new Result(), result); + } + + @Test + void testGetChannelTypeAll() { + when(boardService.getChannelTypeAll(anyString(), anyString())).thenReturn("getChannelTypeAllResponse"); + + String result = boardController.getChannelTypeAll("serverId", "boardTypeId"); + Assertions.assertEquals("getChannelTypeAllResponse", result); + } + + @Test + void testDeleteChannelById() { + when(boardService.deleteChannelById(anyString(), anyString())).thenReturn("deleteChannelByIdResponse"); + + String result = boardController.deleteChannelById("serverId", "channelId"); + Assertions.assertEquals("deleteChannelByIdResponse", result); + } + + @Test + void testGetChannelPath() { + when(boardService.getChannelPath(anyString(), anyString())).thenReturn("getChannelPathResponse"); + + String result = boardController.getChannelPath("serverId", "boardId"); + Assertions.assertEquals("getChannelPathResponse", result); + } + + @Test + void testRefressBoardInformation() { + when(boardService.refressBoardInformation(anyString())).thenReturn("refressBoardInformationResponse"); + + String result = boardController.refressBoardInformation("serverId"); + Assertions.assertEquals("refressBoardInformationResponse", result); + } + + @Test + void testGetBoardTemperature() { + when(boardService.getBoardTemperature(any(BoardTemperatureDto.class))).thenReturn("getBoardTemperatureResponse"); + + String result = boardController.getBoardTemperature(new BoardTemperatureDto()); + Assertions.assertEquals("getBoardTemperatureResponse", result); + } +} + +//Generated with love by TestMe :) Please raise issues & feature requests at: https://weirddev.com/forum#!/testme \ No newline at end of file diff --git a/src/test/java/com/cbsd/universaltestsoftware_client/service/impl/BoardServiceImplTest.java b/src/test/java/com/cbsd/universaltestsoftware_client/service/impl/BoardServiceImplTest.java new file mode 100644 index 0000000..27be5ca --- /dev/null +++ b/src/test/java/com/cbsd/universaltestsoftware_client/service/impl/BoardServiceImplTest.java @@ -0,0 +1,48 @@ +package com.cbsd.universaltestsoftware_client.service.impl; + +import com.cbsd.universaltestsoftware_client.dto.PageBoard; +import com.cbsd.universaltestsoftware_client.entity.ServerInfo; +import com.cbsd.universaltestsoftware_client.mapper.ChannelDataTrafficMapper; +import com.cbsd.universaltestsoftware_client.mapper.ParserMapper; +import com.cbsd.universaltestsoftware_client.mapper.ServerMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.Serializable; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +class BoardServiceImplTest { + @Mock + ServerMapper serverMapper; + @Mock + ParserMapper parserMapper; + @Mock + ChannelDataTrafficMapper channelDataTrafficMapper; + @InjectMocks + BoardServiceImpl boardServiceImpl; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testPageList() { + ServerInfo serverInfo = new ServerInfo(); + serverInfo.setConnectStatus(1); + serverInfo.setIpAddress("192.168.100.4"); + serverInfo.setHttpPort(8082); + when(serverMapper.selectById(any(Serializable.class))).thenReturn(serverInfo); + + String result = boardServiceImpl.pageList(new PageBoard("1960241264519610369")); + Assertions.assertTrue(result.contains("\"code\":1")); + } +} + +//Generated with love by TestMe :) Please raise issues & feature requests at: https://weirddev.com/forum#!/testme \ No newline at end of file diff --git a/update_channel_data_structure.sql b/update_channel_data_structure.sql new file mode 100644 index 0000000..8a6f282 --- /dev/null +++ b/update_channel_data_structure.sql @@ -0,0 +1,18 @@ +# 第一步 新增new_id字段 +ALTER TABLE channel_data ADD COLUMN new_id INT NOT NULL FIRST; + +# 第二步 给new_id赋值 +SET @row_num = 0; +UPDATE channel_data SET new_id = (@row_num := @row_num + 1); + +# 第三步 删除原主键,将new_id设为主键 +ALTER TABLE channel_data DROP PRIMARY KEY, ADD PRIMARY KEY (new_id); + +# 第四步 将new_id设置为自增 +ALTER TABLE channel_data MODIFY COLUMN new_id INT NOT NULL AUTO_INCREMENT; + +# 第五步 删除原data_id字段 +ALTER TABLE channel_data DROP COLUMN data_id; + +# 第六步 重命名new_id字段为data_id +ALTER TABLE channel_data CHANGE COLUMN new_id data_id INT NOT NULL AUTO_INCREMENT;