Browse Source

first commit

master
王梦远 5 days ago
commit
da0b10a714
  1. 3
      .gitattributes
  2. 37
      .gitignore
  3. 61
      build.gradle
  4. BIN
      gradle/wrapper/gradle-wrapper.jar
  5. 7
      gradle/wrapper/gradle-wrapper.properties
  6. 251
      gradlew
  7. 94
      gradlew.bat
  8. BIN
      lib/modbus4j-3.1.0.jar
  9. BIN
      lib/opencv-420.jar
  10. 1
      settings.gradle
  11. 21
      src/main/java/com/iflytop/handacid/HandAcidApplication.java
  12. 28
      src/main/java/com/iflytop/handacid/app/command/control/StopAllMotorCommand.java
  13. 6
      src/main/java/com/iflytop/handacid/app/common/annotation/CheckedRunnable.java
  14. 12
      src/main/java/com/iflytop/handacid/app/common/annotation/CommandDebugMapping.java
  15. 12
      src/main/java/com/iflytop/handacid/app/common/annotation/CommandMapping.java
  16. 47
      src/main/java/com/iflytop/handacid/app/common/constant/CommandStatus.java
  17. 12
      src/main/java/com/iflytop/handacid/app/common/enums/ModuleStateCode.java
  18. 11
      src/main/java/com/iflytop/handacid/app/common/enums/MultipleModuleCode.java
  19. 7
      src/main/java/com/iflytop/handacid/app/common/enums/UserRole.java
  20. 82
      src/main/java/com/iflytop/handacid/app/common/handler/GlobalExceptionHandler.java
  21. 36
      src/main/java/com/iflytop/handacid/app/common/handler/MyMetaObjectHandler.java
  22. 23
      src/main/java/com/iflytop/handacid/app/common/utils/CommandUtil.java
  23. 16
      src/main/java/com/iflytop/handacid/app/common/utils/LambdaUtil.java
  24. 21
      src/main/java/com/iflytop/handacid/app/config/AsyncConfig.java
  25. 56
      src/main/java/com/iflytop/handacid/app/controller/AuthController.java
  26. 67
      src/main/java/com/iflytop/handacid/app/controller/CmdController.java
  27. 67
      src/main/java/com/iflytop/handacid/app/controller/CmdDebugController.java
  28. 53
      src/main/java/com/iflytop/handacid/app/controller/SolutionsController.java
  29. 53
      src/main/java/com/iflytop/handacid/app/controller/SystemConfigController.java
  30. 45
      src/main/java/com/iflytop/handacid/app/controller/SystemController.java
  31. 53
      src/main/java/com/iflytop/handacid/app/controller/SystemLogController.java
  32. 45
      src/main/java/com/iflytop/handacid/app/controller/TestController.java
  33. 55
      src/main/java/com/iflytop/handacid/app/controller/UserController.java
  34. 45
      src/main/java/com/iflytop/handacid/app/core/aspect/DeviceStateChangeAspect.java
  35. 22
      src/main/java/com/iflytop/handacid/app/core/command/BaseCommandHandler.java
  36. 55
      src/main/java/com/iflytop/handacid/app/core/command/CommandDebugHandlerRegistry.java
  37. 59
      src/main/java/com/iflytop/handacid/app/core/command/CommandFuture.java
  38. 10
      src/main/java/com/iflytop/handacid/app/core/command/CommandHandler.java
  39. 55
      src/main/java/com/iflytop/handacid/app/core/command/CommandHandlerRegistry.java
  40. 50
      src/main/java/com/iflytop/handacid/app/core/command/CommandPoolManager.java
  41. 39
      src/main/java/com/iflytop/handacid/app/core/command/CyclicNumberGenerator.java
  42. 35
      src/main/java/com/iflytop/handacid/app/core/command/DeviceCommand.java
  43. 1265
      src/main/java/com/iflytop/handacid/app/core/command/DeviceCommandGenerator.java
  44. 16
      src/main/java/com/iflytop/handacid/app/core/event/CommandFeedbackEvent.java
  45. 14
      src/main/java/com/iflytop/handacid/app/core/event/StateChangeEvent.java
  46. 16
      src/main/java/com/iflytop/handacid/app/core/event/VirtualDeviceCmdResponseEvent.java
  47. 20
      src/main/java/com/iflytop/handacid/app/core/listener/VirtualDeviceCmdResponseEventListener.java
  48. 53
      src/main/java/com/iflytop/handacid/app/core/state/DeviceState.java
  49. 73
      src/main/java/com/iflytop/handacid/app/model/dto/CommandDTO.java
  50. 22
      src/main/java/com/iflytop/handacid/app/model/dto/LoginDTO.java
  51. 16
      src/main/java/com/iflytop/handacid/app/model/dto/TimeSetDTO.java
  52. 19
      src/main/java/com/iflytop/handacid/app/model/dto/TrayTubeSetExistDTO.java
  53. 16
      src/main/java/com/iflytop/handacid/app/model/vo/TimeResponseVO.java
  54. 172
      src/main/java/com/iflytop/handacid/app/service/DeviceCommandService.java
  55. 47
      src/main/java/com/iflytop/handacid/app/service/DeviceInitService.java
  56. 119
      src/main/java/com/iflytop/handacid/app/service/DeviceParamConfigService.java
  57. 27
      src/main/java/com/iflytop/handacid/app/service/SystemService.java
  58. 15
      src/main/java/com/iflytop/handacid/app/service/TestService.java
  59. 45
      src/main/java/com/iflytop/handacid/app/service/VirtualDeviceService.java
  60. 22
      src/main/java/com/iflytop/handacid/app/websocket/server/DebugGenerator.java
  61. 59
      src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketMessageType.java
  62. 59
      src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketSender.java
  63. 51
      src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServer.java
  64. 14
      src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServerConfig.java
  65. 21
      src/main/java/com/iflytop/handacid/app/websocket/server/WebsocketResult.java
  66. 44
      src/main/java/com/iflytop/handacid/common/base/BaseEntity.java
  67. 26
      src/main/java/com/iflytop/handacid/common/base/BasePageQuery.java
  68. 97
      src/main/java/com/iflytop/handacid/common/base/IBaseEnum.java
  69. 24
      src/main/java/com/iflytop/handacid/common/command/DeviceCommandParams.java
  70. 32
      src/main/java/com/iflytop/handacid/common/config/A8kCanBusConnectionConfig.java
  71. 61
      src/main/java/com/iflytop/handacid/common/config/MybatisPlusConfig.java
  72. 68
      src/main/java/com/iflytop/handacid/common/config/SwaggerConfig.java
  73. 16
      src/main/java/com/iflytop/handacid/common/config/WebConfig.java
  74. 39
      src/main/java/com/iflytop/handacid/common/enums/Action.java
  75. 19
      src/main/java/com/iflytop/handacid/common/enums/BeepMode.java
  76. 66
      src/main/java/com/iflytop/handacid/common/enums/Device.java
  77. 5
      src/main/java/com/iflytop/handacid/common/enums/EnableStatus.java
  78. 25
      src/main/java/com/iflytop/handacid/common/enums/HardwareType.java
  79. 5
      src/main/java/com/iflytop/handacid/common/enums/MotorDirection.java
  80. 17
      src/main/java/com/iflytop/handacid/common/enums/TricolorLightColor.java
  81. 21
      src/main/java/com/iflytop/handacid/common/exception/AppException.java
  82. 12
      src/main/java/com/iflytop/handacid/common/mapper/DeviceParamConfigMapper.java
  83. 13
      src/main/java/com/iflytop/handacid/common/mapper/SolutionsMapper.java
  84. 12
      src/main/java/com/iflytop/handacid/common/mapper/SystemConfigMapper.java
  85. 12
      src/main/java/com/iflytop/handacid/common/mapper/SystemLogMapper.java
  86. 12
      src/main/java/com/iflytop/handacid/common/mapper/UserMapper.java
  87. 20
      src/main/java/com/iflytop/handacid/common/model/bo/Point2D.java
  88. 22
      src/main/java/com/iflytop/handacid/common/model/bo/Point3D.java
  89. 30
      src/main/java/com/iflytop/handacid/common/model/entity/DeviceParamConfig.java
  90. 17
      src/main/java/com/iflytop/handacid/common/model/entity/Solutions.java
  91. 21
      src/main/java/com/iflytop/handacid/common/model/entity/SystemConfig.java
  92. 20
      src/main/java/com/iflytop/handacid/common/model/entity/SystemLog.java
  93. 44
      src/main/java/com/iflytop/handacid/common/model/entity/User.java
  94. 36
      src/main/java/com/iflytop/handacid/common/model/vo/DeviceParamGroupVO.java
  95. 19
      src/main/java/com/iflytop/handacid/common/model/vo/ModuleIdVO.java
  96. 16
      src/main/java/com/iflytop/handacid/common/model/vo/RegIndexVO.java
  97. 12
      src/main/java/com/iflytop/handacid/common/result/IResultCode.java
  98. 43
      src/main/java/com/iflytop/handacid/common/result/PageResult.java
  99. 75
      src/main/java/com/iflytop/handacid/common/result/Result.java
  100. 96
      src/main/java/com/iflytop/handacid/common/result/ResultCode.java

3
.gitattributes

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

37
.gitignore

@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

61
build.gradle

@ -0,0 +1,61 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.3'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.iflytop'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/spring' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'// Spring Boot Spring Context
implementation 'org.springframework.boot:spring-boot-starter-web'// REST API Web Spring MVCTomcat
implementation 'org.springframework.boot:spring-boot-starter-websocket:3.5.3' // WebSocket
implementation 'org.springframework.statemachine:spring-statemachine-core:4.0.1'// Spring
implementation 'org.springframework.boot:spring-boot-starter-validation:3.5.3' // Bean @Valid@NotNull
implementation 'org.springframework.boot:spring-boot-starter-aop'// AOP
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'// OpenAPI Swagger UI
implementation 'org.freemarker:freemarker:2.3.34'// FreeMarker Web
implementation 'org.xerial:sqlite-jdbc:3.50.3.0'// SQLite JDBC SQLite
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.5' // MyBatis Spring Boot MyBatis
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.12'// MyBatis Plus MyBatis CRUD
implementation 'com.baomidou:mybatis-plus-generator:3.5.12'// MyBatis Plus MapperService
implementation 'com.baomidou:mybatis-plus-jsqlparser:3.5.12'// MyBatis Plus SQL MyBatis Plus
implementation 'ch.qos.logback:logback-core:1.5.16' // Logback
implementation 'cn.hutool:hutool-all:5.8.39'// Hutool IO
implementation 'com.github.xiaoymin:knife4j-openapi3-jakarta-spring-boot-starter:4.5.0'// Knife4j Swagger API
implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' // Jakarta @PostConstruct@Resource
implementation 'com.fazecast:jSerialComm:2.11.2'// jSerialComm
implementation 'com.opencsv:opencsv:5.11.2'// CSV OpenCSV CSV
implementation 'org.java-websocket:Java-WebSocket:1.6.0'
implementation fileTree(dir: 'lib', include: '*.jar')// lib jar
compileOnly 'org.projectlombok:lombok' // Lombok Java @Getter @Setter
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'// JUnitMockito
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'// JUnit JUnit Platform IDE
}
bootJar {
launchScript {
properties 'spring.profiles.active': 'prod'
}
}
tasks.named('test') {
useJUnitPlatform()
}

BIN
gradle/wrapper/gradle-wrapper.jar

7
gradle/wrapper/gradle-wrapper.properties

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v8.14.3/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
gradlew

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed 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
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
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
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
lib/modbus4j-3.1.0.jar

BIN
lib/opencv-420.jar

1
settings.gradle

@ -0,0 +1 @@
rootProject.name = 'hand-acid'

21
src/main/java/com/iflytop/handacid/HandAcidApplication.java

@ -0,0 +1,21 @@
package com.iflytop.handacid;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
@SpringBootApplication
public class HandAcidApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(HandAcidApplication.class);
ConfigurableApplicationContext ctx = app.run(args);
ConfigurableEnvironment env = ctx.getEnvironment();
String port = env.getProperty("local.server.port", env.getProperty("server.port", "8080"));
System.out.println("应用已启动");
System.out.println("访问地址 → http://localhost:" + port + "/");
System.out.println("文档地址 → http://localhost:" + port + "/doc.html");
}
}

28
src/main/java/com/iflytop/handacid/app/command/control/StopAllMotorCommand.java

@ -0,0 +1,28 @@
package com.iflytop.handacid.app.command.control;
import com.iflytop.handacid.app.common.annotation.CommandMapping;
import com.iflytop.handacid.app.core.command.BaseCommandHandler;
import com.iflytop.handacid.app.model.dto.CommandDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
/**
* 停止所有电机
*/
@Slf4j
@Component
@RequiredArgsConstructor
@CommandMapping("stop_all_motor")
public class StopAllMotorCommand extends BaseCommandHandler {
@Override
public CompletableFuture<Void> handle(CommandDTO commandDTO) {
return runAsync(() -> {
});
}
}

6
src/main/java/com/iflytop/handacid/app/common/annotation/CheckedRunnable.java

@ -0,0 +1,6 @@
package com.iflytop.handacid.app.common.annotation;
@FunctionalInterface
public interface CheckedRunnable {
void run() throws Exception;
}

12
src/main/java/com/iflytop/handacid/app/common/annotation/CommandDebugMapping.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.app.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CommandDebugMapping {
String value();
}

12
src/main/java/com/iflytop/handacid/app/common/annotation/CommandMapping.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.app.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CommandMapping {
String value();
}

47
src/main/java/com/iflytop/handacid/app/common/constant/CommandStatus.java

@ -0,0 +1,47 @@
package com.iflytop.handacid.app.common.constant;
public class CommandStatus {
/**
* 普通消息反馈
*/
public static final String SEND = "send";
/**
* 收到指令
*/
public static final String RECEIVE = "receive";
/**
* 开始执行指令
*/
public static final String START = "start";
/**
* 等待执行指令
*/
public static final String WAIT = "wait";
/**
* 执行成功
*/
public static final String SUCCESS = "success";
/**
* 执行失败
*/
public static final String FAIL = "fail";
/**
* 指令处理完毕反馈
*/
public static final String FINISH = "finish";
/**
* 设备指令 发送设备指令
*/
public static final String DEVICE_SEND = "device_send";
/**
* 设备指令 收到设备指令反馈
*/
public static final String DEVICE_RESULT = "device_result";
/**
* 设备指令 收到设备指令反馈 错误
*/
public static final String DEVICE_ERROR = "device_error";
}

12
src/main/java/com/iflytop/handacid/app/common/enums/ModuleStateCode.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.app.common.enums;
/**
* 多模块枚举类型
*/
public enum ModuleStateCode {
ADD,
PRE,
DRAIN,
IDLE,
}

11
src/main/java/com/iflytop/handacid/app/common/enums/MultipleModuleCode.java

@ -0,0 +1,11 @@
package com.iflytop.handacid.app.common.enums;
/**
* 多模块枚举类型
*/
public enum MultipleModuleCode {
MODULE_1,
MODULE_2,
MODULE_3,
MODULE_4,
}

7
src/main/java/com/iflytop/handacid/app/common/enums/UserRole.java

@ -0,0 +1,7 @@
package com.iflytop.handacid.app.common.enums;
public enum UserRole {
ADMIN,
DEVELOPER,
USER;
}

82
src/main/java/com/iflytop/handacid/app/common/handler/GlobalExceptionHandler.java

@ -0,0 +1,82 @@
package com.iflytop.handacid.app.common.handler;
import com.iflytop.handacid.common.exception.AppException;
import com.iflytop.handacid.common.result.Result;
import com.iflytop.handacid.common.result.ResultCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result<?> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
String method = request.getMethod();
String url = request.getRequestURL().toString();
String allowed = ex.getSupportedHttpMethods() != null
? ex.getSupportedHttpMethods().toString()
: "[]";
String msg = String.format(
"请求方法 '%s' 不支持 (URL: %s)。支持的方法有:%s",
method, url, allowed);
log.error(msg, ex);
return Result.failed(ResultCode.METHOD_NOT_ALLOWED.getCode(), msg);
}
// 1) JSON Body 校验失败
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleBodyValid(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldErrors().stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining("; "));
return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg);
}
// 2) 方法级参数校验失败PathVariable/RequestParam
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleParamValid(ConstraintViolationException ex) {
String msg = ex.getConstraintViolations().stream()
.map(v -> {
String path = v.getPropertyPath().toString();
String field = path.substring(path.lastIndexOf('.') + 1);
return field + ": " + v.getMessage();
})
.collect(Collectors.joining("; "));
return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg);
}
// 3) 表单绑定失败
@ExceptionHandler(BindException.class)
public Result<?> handleBind(BindException ex) {
String msg = ex.getFieldErrors().stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining("; "));
return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg);
}
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception ex, HttpServletRequest request) {
// 获取请求方法和请求路径
String method = request.getMethod();
String url = request.getRequestURL().toString();
if (ex instanceof AppException ae) {
log.warn("AppException at {} {}:", method, url, ae);
return Result.failed(ae.getResultCode());
}
// 在日志中打印出请求地址
log.error("Unhandled exception at {} {}:", method, url, ex);
return Result.failed(ResultCode.SYSTEM_ERROR.getCode(), ex.getMessage());
}
}

36
src/main/java/com/iflytop/handacid/app/common/handler/MyMetaObjectHandler.java

@ -0,0 +1,36 @@
package com.iflytop.handacid.app.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* mybatis-plus 字段自动填充
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 新增填充创建时间
*
* @param metaObject 元数据
*/
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
/**
* 更新填充更新时间
*
* @param metaObject 元数据
*/
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
}

23
src/main/java/com/iflytop/handacid/app/common/utils/CommandUtil.java

@ -0,0 +1,23 @@
package com.iflytop.handacid.app.common.utils;
import com.iflytop.handacid.app.core.command.CommandFuture;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class CommandUtil {
public static void wait(CommandFuture... futures) throws Exception {
wait(120L, futures);
}
public static void wait(Long timeout, CommandFuture... futures) throws Exception {
CompletableFuture<?>[] responseFutures = Arrays.stream(futures)
.map(CommandFuture::getResponseFuture)
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(responseFutures)
.get(timeout, TimeUnit.SECONDS);
}
}

16
src/main/java/com/iflytop/handacid/app/common/utils/LambdaUtil.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.app.common.utils;
import com.iflytop.handacid.app.common.annotation.CheckedRunnable;
public class LambdaUtil {
public static Runnable unchecked(CheckedRunnable runnable) {
return () -> {
try {
runnable.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}

21
src/main/java/com/iflytop/handacid/app/config/AsyncConfig.java

@ -0,0 +1,21 @@
package com.iflytop.handacid.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class AsyncConfig {
@Bean("commandTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setCorePoolSize(10);
exec.setMaxPoolSize(20);
exec.setQueueCapacity(50);
exec.setThreadNamePrefix("cmd-exec-");
exec.setWaitForTasksToCompleteOnShutdown(true);
exec.setAwaitTerminationSeconds(30);
exec.initialize();
return exec;
}
}

56
src/main/java/com/iflytop/handacid/app/controller/AuthController.java

@ -0,0 +1,56 @@
package com.iflytop.handacid.app.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.iflytop.handacid.app.core.state.DeviceState;
import com.iflytop.handacid.app.model.dto.LoginDTO;
import com.iflytop.handacid.common.enums.EnableStatus;
import com.iflytop.handacid.common.model.entity.User;
import com.iflytop.handacid.common.result.Result;
import com.iflytop.handacid.common.result.ResultCode;
import com.iflytop.handacid.common.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
/**
* 认证控制
*/
@Tag(name = "\uD83D\uDD11认证")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {
private final UserService userService;
private final DeviceState deviceState;
@Operation(summary = "账号密码登录")
@PostMapping("/login")
public Result<User> login(@Valid @RequestBody LoginDTO loginDTO) {
User user = userService.getOne(new LambdaQueryWrapper<>(User.class).eq(User::getUsername, loginDTO.getUsername()));
if (user != null && !Objects.equals(user.getDeleted(), EnableStatus.ENABLE) && user.getPassword().equals(loginDTO.getPassword())) {
deviceState.setCurrentUser(user);
user.setPassword(null);
return Result.success(user);
}
return Result.failed(ResultCode.INVALID_CREDENTIALS);
}
@Operation(summary = "用户登出")
@PostMapping("/logout")
public Result<String> logout() {
deviceState.setCurrentUser(null);
return Result.success();
}
@Operation(summary = "获取当前登录用户")
@GetMapping("/current")
public Result<User> current() {
return Result.success(deviceState.getCurrentUser());
}
}

67
src/main/java/com/iflytop/handacid/app/controller/CmdController.java

@ -0,0 +1,67 @@
package com.iflytop.handacid.app.controller;
import cn.hutool.json.JSONUtil;
import com.iflytop.handacid.app.common.constant.CommandStatus;
import com.iflytop.handacid.app.core.command.CommandHandler;
import com.iflytop.handacid.app.core.command.CommandHandlerRegistry;
import com.iflytop.handacid.app.model.dto.CommandDTO;
import com.iflytop.handacid.app.websocket.server.DebugGenerator;
import com.iflytop.handacid.app.websocket.server.WebSocketSender;
import com.iflytop.handacid.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 java.util.concurrent.CompletableFuture;
@Tag(name = "前端业务指令")
@RestController
@RequestMapping("/api/cmd")
@RequiredArgsConstructor
@Slf4j
public class CmdController {
private final CommandHandlerRegistry registry;
private final WebSocketSender webSocketService;
@Operation(summary = "前端统一调用一个接口")
@PostMapping
public Result<?> controlMethod(@Valid @RequestBody CommandDTO commandDTO){
String commandId = commandDTO.getCommandId();
String command = commandDTO.getCommand();
try {
webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到业务指令请求,开始处理"));
CommandHandler commandHandler = registry.getCommandHandler(command);
if (commandHandler == null) {
webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的业务指令"));
log.error("未找到对应的业务指令");
return Result.failed();
}
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "业务指令开始执行"));
log.info("业务指令开始执行");
CompletableFuture<Void> future = commandHandler.handle(commandDTO);
future.whenComplete((v, ex) -> {
if (ex != null) {
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行业务指令发生异常", ex.getMessage()));
log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), ex);
} else {
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "业务指令执行成功"));
log.info("业务指令执行成功");
}
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "业务指令执行结束"));
log.info("业务指令执行结束");
});
} catch (Exception e) {
log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), e);
return Result.failed(e.getMessage());
}
return Result.success();
}
}

67
src/main/java/com/iflytop/handacid/app/controller/CmdDebugController.java

@ -0,0 +1,67 @@
package com.iflytop.handacid.app.controller;
import cn.hutool.json.JSONUtil;
import com.iflytop.handacid.app.common.constant.CommandStatus;
import com.iflytop.handacid.app.core.command.CommandDebugHandlerRegistry;
import com.iflytop.handacid.app.core.command.CommandHandler;
import com.iflytop.handacid.app.model.dto.CommandDTO;
import com.iflytop.handacid.app.websocket.server.DebugGenerator;
import com.iflytop.handacid.app.websocket.server.WebSocketSender;
import com.iflytop.handacid.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 java.util.concurrent.CompletableFuture;
@Tag(name = "前端调试指令")
@RestController
@RequestMapping("/api/debug/cmd")
@RequiredArgsConstructor
@Slf4j
public class CmdDebugController {
private final CommandDebugHandlerRegistry registry;
private final WebSocketSender webSocketService;
@Operation(summary = "前端调试指令")
@PostMapping
public Result<?> controlMethod(@Valid @RequestBody CommandDTO commandDTO) {
String commandId = commandDTO.getCommandId();
String command = commandDTO.getCommand();
try {
webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到调试指令请求,开始处理"));
CommandHandler commandHandler = registry.getCommandHandler(command);
if (commandHandler == null) {
webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的调试指令"));
log.error("未找到对应的调试指令");
return Result.failed();
}
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "调试指令开始执行"));
log.info("调试指令开始执行");
CompletableFuture<Void> future = commandHandler.handle(commandDTO);
future.whenComplete((v, ex) -> {
if (ex != null) {
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行调试指令发生异常", ex.getMessage()));
log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), ex);
} else {
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "调试指令执行成功"));
log.info("调试指令执行成功");
}
webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "调试指令执行结束"));
log.info("调试指令执行结束");
});
} catch (Exception e) {
log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), e);
return Result.failed(e.getMessage());
}
return Result.success();
}
}

53
src/main/java/com/iflytop/handacid/app/controller/SolutionsController.java

@ -0,0 +1,53 @@
package com.iflytop.handacid.app.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.iflytop.handacid.common.base.BasePageQuery;
import com.iflytop.handacid.common.model.entity.Solutions;
import com.iflytop.handacid.common.result.PageResult;
import com.iflytop.handacid.common.result.Result;
import com.iflytop.handacid.common.service.SolutionsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
@Tag(name = "\uD83D\uDCA7溶液接口")
@RestController
@RequestMapping("/api/solutions")
@RequiredArgsConstructor
@Slf4j
public class SolutionsController {
private final SolutionsService solutionsService;
@Operation(summary = "分页查询溶液")
@PostMapping("/list")
public PageResult<Solutions> list(@RequestBody BasePageQuery query) {
return PageResult.success(solutionsService.page(new Page<>(query.getPageNum(), query.getPageSize())));
}
@Operation(summary = "添加溶液")
@PostMapping("")
public Result<String> add(@Valid @RequestBody Solutions solutions) {
return solutionsService.save(solutions) ? Result.success() : Result.failed();
}
@Operation(summary = "修改溶液")
@PutMapping("")
public Result<String> update(@Valid @RequestBody Solutions solutions) {
return solutionsService.updateById(solutions) ? Result.success() : Result.failed();
}
@Operation(summary = "删除溶液")
@DeleteMapping("/{ids}")
public Result<String> delete(@PathVariable String ids) {
boolean success = solutionsService.removeBatchByIds(
Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
);
return success ? Result.success() : Result.failed();
}
}

53
src/main/java/com/iflytop/handacid/app/controller/SystemConfigController.java

@ -0,0 +1,53 @@
package com.iflytop.handacid.app.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.iflytop.handacid.common.base.BasePageQuery;
import com.iflytop.handacid.common.model.entity.SystemConfig;
import com.iflytop.handacid.common.result.PageResult;
import com.iflytop.handacid.common.result.Result;
import com.iflytop.handacid.common.service.SystemConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
@Tag(name = "\uD83D\uDEE0\uFE0F系统配置接口")
@RestController
@RequestMapping("/api/system-config")
@RequiredArgsConstructor
@Slf4j
public class SystemConfigController {
private final SystemConfigService systemConfigService;
@Operation(summary = "分页查询系统配置")
@PostMapping("/list")
public PageResult<SystemConfig> list(@RequestBody BasePageQuery query) {
return PageResult.success(systemConfigService.page(new Page<>(query.getPageNum(), query.getPageSize())));
}
@Operation(summary = "添加配置项")
@PostMapping("")
public Result<String> add(@Valid @RequestBody SystemConfig config) {
return systemConfigService.save(config) ? Result.success() : Result.failed();
}
@Operation(summary = "更新配置项")
@PutMapping("")
public Result<String> update(@Valid @RequestBody SystemConfig config) {
return systemConfigService.updateById(config) ? Result.success() : Result.failed();
}
@Operation(summary = "删除配置项")
@DeleteMapping("/{ids}")
public Result<String> delete(@PathVariable String ids) {
boolean success = systemConfigService.removeBatchByIds(
Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
);
return success ? Result.success() : Result.failed();
}
}

45
src/main/java/com/iflytop/handacid/app/controller/SystemController.java

@ -0,0 +1,45 @@
package com.iflytop.handacid.app.controller;
import com.iflytop.handacid.app.core.state.DeviceState;
import com.iflytop.handacid.app.model.dto.TimeSetDTO;
import com.iflytop.handacid.app.model.vo.TimeResponseVO;
import com.iflytop.handacid.app.service.SystemService;
import com.iflytop.handacid.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
@Tag(name = "⚙\uFE0F系统")
@RestController
@RequestMapping("/api/sys")
@RequiredArgsConstructor
@Slf4j
public class SystemController {
private final SystemService systemService;
private final DeviceState deviceState;
@Operation(summary = "获取当前设备状态")
@GetMapping("/device-status")
public Result<?> getDeviceStatus() {
return Result.success(deviceState.toJSON());
}
@Operation(summary = "设置系统时间")
@PostMapping("/set-datetime")
public Result<?> setDatetime(@Valid @RequestBody TimeSetDTO timeSetDTO) {
systemService.setSystemTime(timeSetDTO.getEpochMilli());
return Result.success();
}
@Operation(summary = "获取当前系统时间")
@GetMapping("/get-datetime")
public Result<TimeResponseVO> getDatetime() {
return Result.success(new TimeResponseVO(Instant.now().toEpochMilli()));
}
}

53
src/main/java/com/iflytop/handacid/app/controller/SystemLogController.java

@ -0,0 +1,53 @@
package com.iflytop.handacid.app.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.iflytop.handacid.common.base.BasePageQuery;
import com.iflytop.handacid.common.model.entity.SystemLog;
import com.iflytop.handacid.common.result.PageResult;
import com.iflytop.handacid.common.result.Result;
import com.iflytop.handacid.common.service.SystemLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
@Tag(name = "\uD83D\uDCDD系统日志接口")
@RestController
@RequestMapping("/api/system-log")
@RequiredArgsConstructor
@Slf4j
public class SystemLogController {
private final SystemLogService systemLogService;
@Operation(summary = "分页查询系统日志")
@PostMapping("/list")
public PageResult<SystemLog> list(@RequestBody BasePageQuery query) {
return PageResult.success(systemLogService.page(new Page<>(query.getPageNum(), query.getPageSize())));
}
@Operation(summary = "添加日志")
@PostMapping("")
public Result<String> add(@Valid @RequestBody SystemLog logEntry) {
return systemLogService.save(logEntry) ? Result.success() : Result.failed();
}
@Operation(summary = "更新日志")
@PutMapping("")
public Result<String> update(@Valid @RequestBody SystemLog logEntry) {
return systemLogService.updateById(logEntry) ? Result.success() : Result.failed();
}
@Operation(summary = "删除日志")
@DeleteMapping("/{ids}")
public Result<String> delete(@PathVariable String ids) {
boolean success = systemLogService.removeBatchByIds(
Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
);
return success ? Result.success() : Result.failed();
}
}

45
src/main/java/com/iflytop/handacid/app/controller/TestController.java

@ -0,0 +1,45 @@
package com.iflytop.handacid.app.controller;
import com.iflytop.handacid.app.common.enums.MultipleModuleCode;
import com.iflytop.handacid.app.core.state.DeviceState;
import com.iflytop.handacid.app.service.TestService;
import com.iflytop.handacid.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试用
*/
@Tag(name = "⭐测试用")
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
@Slf4j
public class TestController {
private final DeviceState deviceState;
@Operation(summary = "启动虚拟模式")
@PostMapping("/virtual")
public Result<?> changeVirtualMode() {
deviceState.setVirtual(true);
return Result.success();
}
@Operation(summary = "启动虚拟模式并且自检完毕")
@PostMapping("/virtual-finish")
public Result<?> selfTestFinish() {
deviceState.setVirtual(true);
return Result.success();
}
@Operation(summary = "设置模拟环境湿度")
@PostMapping("/set-humidity")
public Result<?> setHumidity(MultipleModuleCode heatModule, double humidity) {
return Result.success();
}
}

55
src/main/java/com/iflytop/handacid/app/controller/UserController.java

@ -0,0 +1,55 @@
package com.iflytop.handacid.app.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.iflytop.handacid.common.base.BasePageQuery;
import com.iflytop.handacid.common.model.entity.User;
import com.iflytop.handacid.common.result.PageResult;
import com.iflytop.handacid.common.result.Result;
import com.iflytop.handacid.common.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
@Tag(name = "\uD83D\uDC64用户接口")
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final UserService userService;
@Operation(summary = "分页查询用户列表")
@PostMapping("/list")
public PageResult<User> list(@RequestBody BasePageQuery query) {
Page<User> page = new Page<>(query.getPageNum(), query.getPageSize());
return PageResult.success(userService.page(page));
}
@Operation(summary = "添加用户")
@PostMapping("")
public Result<String> add(@Valid @RequestBody User user) {
return userService.save(user) ? Result.success() : Result.failed();
}
@Operation(summary = "修改用户")
@PutMapping("")
public Result<String> update(@Valid @RequestBody User user) {
return userService.updateById(user) ? Result.success() : Result.failed();
}
@Operation(summary = "删除用户")
@DeleteMapping("/{ids}")
public Result<String> delete(@Parameter(description = "用户ID,多个用逗号分隔") @PathVariable String ids) {
boolean success = userService.removeBatchByIds(
Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
);
return success ? Result.success() : Result.failed();
}
}

45
src/main/java/com/iflytop/handacid/app/core/aspect/DeviceStateChangeAspect.java

@ -0,0 +1,45 @@
package com.iflytop.handacid.app.core.aspect;
import com.iflytop.handacid.app.core.state.DeviceState;
import com.iflytop.handacid.app.websocket.server.WebSocketMessageType;
import com.iflytop.handacid.app.websocket.server.WebSocketSender;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class DeviceStateChangeAspect {
private final WebSocketSender webSocketService;
private final DeviceState deviceState;
private final Lock lock = new ReentrantLock();
@Before("execution(* com.iflytop.handacid.app.core.state.*.set*(..))")
public void beforeSetMethod(JoinPoint joinPoint) {
lock.lock();
}
@After("execution(* com.iflytop.handacid.app.core.state.*.set*(..))")
public void afterSetMethod(JoinPoint joinPoint) {
try {
Object[] methodArgs = joinPoint.getArgs();
if (methodArgs != null && methodArgs.length > 0) {
webSocketService.push(WebSocketMessageType.STATUS, deviceState.toJSON());
}
} catch (Exception e) {
log.error("处理状态变更后的值失败", e);
} finally {
lock.unlock();
}
}
}

22
src/main/java/com/iflytop/handacid/app/core/command/BaseCommandHandler.java

@ -0,0 +1,22 @@
package com.iflytop.handacid.app.core.command;
import com.iflytop.handacid.app.common.annotation.CheckedRunnable;
import com.iflytop.handacid.app.common.utils.LambdaUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public abstract class BaseCommandHandler implements CommandHandler {
@Autowired
@Qualifier("commandTaskExecutor")
private Executor commandExecutor;
protected CompletableFuture<Void> runAsync(CheckedRunnable task) {
return CompletableFuture.runAsync(LambdaUtil.unchecked(task), commandExecutor);
}
}

55
src/main/java/com/iflytop/handacid/app/core/command/CommandDebugHandlerRegistry.java

@ -0,0 +1,55 @@
package com.iflytop.handacid.app.core.command;
import com.iflytop.handacid.app.common.annotation.CommandDebugMapping;
import com.iflytop.handacid.common.exception.AppException;
import com.iflytop.handacid.common.result.ResultCode;
import io.micrometer.common.lang.NonNull;
import jakarta.annotation.PostConstruct;
import jakarta.validation.constraints.NotNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class CommandDebugHandlerRegistry implements ApplicationContextAware {
private final Map<String, CommandHandler> handlerMap = new HashMap<>();
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@PostConstruct
public void init() {
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(CommandDebugMapping.class);
for (Object bean : beans.values()) {
// 获取实际目标类而不是代理类
Class<?> targetClass = AopUtils.getTargetClass(bean);
CommandDebugMapping mapping = targetClass.getAnnotation(CommandDebugMapping.class);
if (mapping != null && bean instanceof CommandHandler) {
String mappingKey = mapping.value();
handlerMap.put(mappingKey, (CommandHandler) bean);
}
}
}
/**
* 通过模块名称和命令名称获取命令处理器
*
* @param commandName 命令名称
* @return 命令处理器
*/
public CommandHandler getCommandHandler(@NotNull String commandName) {
if (!handlerMap.containsKey(commandName)) {
throw new AppException(ResultCode.COMMAND_NOT_FOUND);
}
return handlerMap.get(commandName);
}
}

59
src/main/java/com/iflytop/handacid/app/core/command/CommandFuture.java

@ -0,0 +1,59 @@
package com.iflytop.handacid.app.core.command;
import cn.hutool.json.JSONObject;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Setter
@Getter
public class CommandFuture {
/**
* 用于保存response反馈
*/
private CompletableFuture<JSONObject> responseFuture = new CompletableFuture<>();
/**
* 原始指令
*/
private DeviceCommand deviceCommand;
/**
* 指令发送时间
*/
private long startSendTime;
/**
* 指令反馈时间
*/
private long endSendTime;
/**
* 业务指令id
*/
private String cmdId;
/**
* 业务指令code
*/
private String cmdCode;
/**
* 完成response
*/
public void completeResponse(JSONObject result) {
responseFuture.complete(result);
}
/**
* 异常完成response
*/
public void completeResponseExceptionally(Throwable ex) {
responseFuture.completeExceptionally(ex);
}
/**
* 获取response的json
*/
public JSONObject getResponseResult() throws ExecutionException, InterruptedException {
return responseFuture.get();
}
}

10
src/main/java/com/iflytop/handacid/app/core/command/CommandHandler.java

@ -0,0 +1,10 @@
package com.iflytop.handacid.app.core.command;
import com.iflytop.handacid.app.model.dto.CommandDTO;
import java.util.concurrent.CompletableFuture;
public interface CommandHandler {
CompletableFuture<Void> handle(CommandDTO commandDTO) throws Exception;
}

55
src/main/java/com/iflytop/handacid/app/core/command/CommandHandlerRegistry.java

@ -0,0 +1,55 @@
package com.iflytop.handacid.app.core.command;
import com.iflytop.handacid.app.common.annotation.CommandMapping;
import com.iflytop.handacid.common.exception.AppException;
import com.iflytop.handacid.common.result.ResultCode;
import io.micrometer.common.lang.NonNull;
import jakarta.annotation.PostConstruct;
import jakarta.validation.constraints.NotNull;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class CommandHandlerRegistry implements ApplicationContextAware {
private final Map<String, CommandHandler> handlerMap = new HashMap<>();
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@PostConstruct
public void init() {
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(CommandMapping.class);
for (Object bean : beans.values()) {
// 获取实际目标类而不是代理类
Class<?> targetClass = AopUtils.getTargetClass(bean);
CommandMapping mapping = targetClass.getAnnotation(CommandMapping.class);
if (mapping != null && bean instanceof CommandHandler) {
String mappingKey = mapping.value();
handlerMap.put(mappingKey, (CommandHandler) bean);
}
}
}
/**
* 通过模块名称和命令名称获取命令处理器
*
* @param commandName 命令名称
* @return 命令处理器
*/
public CommandHandler getCommandHandler(@NotNull String commandName){
if (!handlerMap.containsKey(commandName)) {
throw new AppException(ResultCode.COMMAND_NOT_FOUND);
}
return handlerMap.get(commandName);
}
}

50
src/main/java/com/iflytop/handacid/app/core/command/CommandPoolManager.java

@ -0,0 +1,50 @@
package com.iflytop.handacid.app.core.command;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@Component
public class CommandPoolManager {
private final ThreadPoolTaskExecutor executor;
public CommandPoolManager(@Qualifier("commandTaskExecutor") ThreadPoolTaskExecutor executor) {
this.executor = executor;
}
/**
* 强制中断所有正在执行的任务
*/
public void forceShutdownAll() {
try {
ThreadPoolExecutor nativeExecutor = executor.getThreadPoolExecutor();
nativeExecutor.shutdownNow();
} catch (Exception e) {
log.error("强制关闭所有cmd线程失败");
}
}
/**
* 重启命令线程池
*/
public void restartExecutor() {
try {
Field field = ThreadPoolTaskExecutor.class.getDeclaredField("threadPoolExecutor");
field.setAccessible(true);
field.set(executor, null);
executor.initialize();
log.info("命令线程池已重建并启动:corePoolSize={}, maxPoolSize={}, queueCapacity={}",
executor.getCorePoolSize(),
executor.getMaxPoolSize(),
executor.getQueueCapacity());
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("通过反射重置 threadPoolExecutor 字段失败,无法重启命令线程池", e);
}
}
}

39
src/main/java/com/iflytop/handacid/app/core/command/CyclicNumberGenerator.java

@ -0,0 +1,39 @@
package com.iflytop.handacid.app.core.command;
public class CyclicNumberGenerator {
// 饿汉式单例在类加载时就创建实例
private static final CyclicNumberGenerator INSTANCE = new CyclicNumberGenerator();
// 当前生成的数字初始值为 1
private int currentNumber = 1;
// 私有构造函数防止外部实例化
private CyclicNumberGenerator() {
}
// 提供全局访问点获取单例实例
public static CyclicNumberGenerator getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
CyclicNumberGenerator generator = CyclicNumberGenerator.getInstance();
for (int i = 0; i < 4096; i++) {
System.out.println(generator.generateNumber());
}
}
/**
* 生成 1 255 之间的循环整数
*
* @return 生成的整数
*/
public synchronized int generateNumber() {
int result = currentNumber;
// 每次生成后将当前数字加 1
currentNumber++;
if (currentNumber > 4096) {
currentNumber = 1;
}
return result;
}
}

35
src/main/java/com/iflytop/handacid/app/core/command/DeviceCommand.java

@ -0,0 +1,35 @@
package com.iflytop.handacid.app.core.command;
import com.iflytop.handacid.common.command.DeviceCommandParams;
import com.iflytop.handacid.common.enums.Action;
import com.iflytop.handacid.common.enums.Device;
import lombok.Data;
@Data
public class DeviceCommand {
/**
* 指令ID
*/
private Integer cmdId;
/**
* 指令代码 例如 "device_status_get"
*/
private String cmdCode;
/**
* 目标设备
*/
private Device device;
/**
* 执行动作
*/
private Action action;
/**
* 指令参数
*/
private DeviceCommandParams param;
}

1265
src/main/java/com/iflytop/handacid/app/core/command/DeviceCommandGenerator.java
File diff suppressed because it is too large
View File

16
src/main/java/com/iflytop/handacid/app/core/event/CommandFeedbackEvent.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.app.core.event;
import cn.hutool.json.JSONObject;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class CommandFeedbackEvent extends ApplicationEvent {
private final JSONObject jsonResponse;
public CommandFeedbackEvent(Object source, JSONObject jsonResponse) {
super(source);
this.jsonResponse = jsonResponse;
}
}

14
src/main/java/com/iflytop/handacid/app/core/event/StateChangeEvent.java

@ -0,0 +1,14 @@
package com.iflytop.handacid.app.core.event;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 状态变更事件
*/
@Data
@AllArgsConstructor
public class StateChangeEvent {
private String fieldPath;
private Object oldValue;
private Object newValue;
}

16
src/main/java/com/iflytop/handacid/app/core/event/VirtualDeviceCmdResponseEvent.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.app.core.event;
import com.iflytop.handacid.app.core.command.DeviceCommand;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class VirtualDeviceCmdResponseEvent extends ApplicationEvent {
private final DeviceCommand cmdToDevice;
public VirtualDeviceCmdResponseEvent(Object source, DeviceCommand cmdToDevice) {
super(source);
this.cmdToDevice = cmdToDevice;
}
}

20
src/main/java/com/iflytop/handacid/app/core/listener/VirtualDeviceCmdResponseEventListener.java

@ -0,0 +1,20 @@
package com.iflytop.handacid.app.core.listener;
import com.iflytop.handacid.app.core.event.VirtualDeviceCmdResponseEvent;
import com.iflytop.handacid.app.service.VirtualDeviceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class VirtualDeviceCmdResponseEventListener {
private final VirtualDeviceService virtualDeviceService;
@EventListener
public void handleDeviceTcpMessageEvent(VirtualDeviceCmdResponseEvent event) {
virtualDeviceService.completeCommandResponse(event.getCmdToDevice());
}
}

53
src/main/java/com/iflytop/handacid/app/core/state/DeviceState.java

@ -0,0 +1,53 @@
package com.iflytop.handacid.app.core.state;
import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.iflytop.handacid.app.common.enums.ModuleStateCode;
import com.iflytop.handacid.app.common.enums.MultipleModuleCode;
import com.iflytop.handacid.common.model.entity.User;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@Schema(description = "设备当前状态")
@Data
@Component
@JsonIgnoreProperties(value = {"advisors", "frozen", "preFiltered", "proxyTargetClass", "targetSource", "exposeProxy", "advisorCount", "proxiedInterfaces", "targetClass"})
public class DeviceState {
@Schema(description = "当前设备是否暂停")
private volatile boolean craftsPaused = false;
@Schema(description = "通道1状态")
private volatile ModuleStateCode MODULE_1 = ModuleStateCode.IDLE;
@Schema(description = "通道2状态")
private volatile ModuleStateCode MODULE_2 = ModuleStateCode.IDLE;
@Schema(description = "通道3状态")
private volatile ModuleStateCode MODULE_3 = ModuleStateCode.IDLE;
@Schema(description = "通道4状态")
private volatile ModuleStateCode MODULE_4 = ModuleStateCode.IDLE;
@Schema(description = "虚拟模式,true为虚拟")
private volatile boolean virtual = false;
@Schema(description = "是否是急停状态,true为急停")
private volatile boolean emergencyStop = false;
@Schema(description = "当前登录用户")
private User currentUser;
public JSONObject toJSON() {
JSONObject json = new JSONObject();
json.putOnce("craftsPaused", craftsPaused);
json.putOnce("virtual", virtual);
json.putOnce("emergencyStop", emergencyStop);
json.putOnce("currentUser", currentUser);
return json;
}
}

73
src/main/java/com/iflytop/handacid/app/model/dto/CommandDTO.java

@ -0,0 +1,73 @@
package com.iflytop.handacid.app.model.dto;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Map;
@Schema(description = "指令")
@Data
public class CommandDTO {
@NotNull
@Schema(description = "指令id,前端生成唯一ID")
private String commandId;
@NotNull
@Schema(description = "命令名称")
private String command;
@Schema(description = "参数")
private Map<String, Object> params;
// 获取 Double 类型的参数null 或空字符串时返回 null
public Double getDoubleParam(String key) {
String value = getStringParam(key);
return (value != null && !value.isEmpty()) ? Double.parseDouble(value) : null;
}
// 获取 String 类型的参数null 或空字符串时返回 null
public String getStringParam(String key) {
Object value = params.get(key);
// 确保值不是 null 或空字符串转换成 String 后返回
return (value != null && !value.toString().isEmpty()) ? value.toString() : null;
}
// 获取 Integer 类型的参数null 或空字符串时返回 null
public Integer getIntegerParam(String key) {
String value = getStringParam(key);
return (value != null && !value.isEmpty()) ? Integer.parseInt(value) : null;
}
// 获取 Long 类型的参数null 或空字符串时返回 null
public Long getLongParam(String key) {
String value = getStringParam(key);
return (value != null && !value.isEmpty()) ? Long.parseLong(value) : null;
}
// 获取 Boolean 类型的参数null 或空字符串时返回 null
public Boolean getBooleanParam(String key) {
String value = getStringParam(key);
return (value != null && !value.isEmpty()) ? Boolean.parseBoolean(value) : null;
}
public JSONObject getJsonObjectParam(String key) {
Object value = params.get(key);
return new JSONObject(value);
}
public JSONArray getJSONArrayParam(String key) {
Object value = params.get(key);
return new JSONArray(value);
}
@Override
public String toString() {
return JSONUtil.toJsonStr(this);
}
}

22
src/main/java/com/iflytop/handacid/app/model/dto/LoginDTO.java

@ -0,0 +1,22 @@
package com.iflytop.handacid.app.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 用户登录表单
*/
@Schema(description = "用户登录")
@Data
public class LoginDTO {
@NotNull
@Schema(description = "用户名", example = "admin")
private String username;
@NotNull
@Schema(description = "用户密码", example = "12345")
private String password;
}

16
src/main/java/com/iflytop/handacid/app/model/dto/TimeSetDTO.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.app.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class TimeSetDTO {
/**
* 要设置的时间戳毫秒UTC 纪元毫秒数
*/
@NotNull
@Schema(description = "要设置的时间戳(毫秒),UTC 纪元毫秒数")
private Long epochMilli;
}

19
src/main/java/com/iflytop/handacid/app/model/dto/TrayTubeSetExistDTO.java

@ -0,0 +1,19 @@
package com.iflytop.handacid.app.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "是否存在试管")
@Data
public class TrayTubeSetExistDTO {
@NotNull
@Schema(description = "试管编号")
private Integer tubeNum;
@NotNull
@Schema(description = "是否存在试管 true存在 false不存在")
private Boolean tubeExist;
}

16
src/main/java/com/iflytop/handacid/app/model/vo/TimeResponseVO.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.app.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class TimeResponseVO {
@Schema(description = "当前系统时间的 UTC 毫秒时间戳")
private long epochMilli;
public TimeResponseVO(long epochMilli) {
this.epochMilli = epochMilli;
}
}

172
src/main/java/com/iflytop/handacid/app/service/DeviceCommandService.java

@ -0,0 +1,172 @@
package com.iflytop.handacid.app.service;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.iflytop.handacid.app.common.constant.CommandStatus;
import com.iflytop.handacid.app.core.command.CommandFuture;
import com.iflytop.handacid.app.core.command.CyclicNumberGenerator;
import com.iflytop.handacid.app.core.command.DeviceCommand;
import com.iflytop.handacid.app.core.event.VirtualDeviceCmdResponseEvent;
import com.iflytop.handacid.app.core.state.DeviceState;
import com.iflytop.handacid.app.websocket.server.DebugGenerator;
import com.iflytop.handacid.app.websocket.server.WebSocketSender;
import com.iflytop.handacid.hardware.HardwareService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceCommandService {
private final HardwareService hardwareService;
private final WebSocketSender webSocketService;
private final DeviceState deviceState;
private final ApplicationEventPublisher publisher;
/**
* 需要等待加液区空闲的龙门架机械臂指令
*/
private final ConcurrentMap<Integer, CommandFuture> sendCommandFutureMap = new ConcurrentHashMap<>();
private final BlockingQueue<CommandFuture[]> gantryCommandQueue = new LinkedBlockingQueue<>();
@PostConstruct
private void initExecutorThread() {
new Thread(this::executeCommands).start();
}
private void executeCommands() {
while (true) {
try {
CommandFuture[] commandFutureArray = gantryCommandQueue.take();
for (CommandFuture commandFuture : commandFutureArray) {
executeCommand(commandFuture);
}
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}
}
public synchronized CommandFuture[] sendCommandGantryQueue(DeviceCommand... deviceCommandBundles) {
return sendCommandGantryQueue(null, null, deviceCommandBundles);
}
public synchronized CommandFuture[] sendCommandGantryQueue(String cmdId, String cmdCode, DeviceCommand... deviceCommands) {
List<CommandFuture> commandFutureList = new ArrayList<>();
for (DeviceCommand deviceCommand : deviceCommands) {
commandFutureList.add(createDeviceCommandFuture(cmdId, cmdCode, deviceCommand));
}
CommandFuture[] commandFutureArray = commandFutureList.toArray(new CommandFuture[0]);
try {
gantryCommandQueue.put(commandFutureArray);
} catch (Exception e) {
log.error("设备指令入队列失败", e);
throw new RuntimeException(e);
}
return commandFutureArray;
}
/**
* 根据 DeviceCommand 创建 CommandFuture
*/
private CommandFuture createDeviceCommandFuture(String cmdId, String cmdCode, DeviceCommand deviceCommand) {
CommandFuture commandFuture = createDeviceCommandFuture(deviceCommand);
commandFuture.setCmdId(cmdId);
commandFuture.setCmdCode(cmdCode);
return commandFuture;
}
/**
* 根据 DeviceCommand 创建 CommandFuture
*/
private CommandFuture createDeviceCommandFuture(DeviceCommand deviceCommand) {
CommandFuture commandFuture = new CommandFuture();
commandFuture.setDeviceCommand(deviceCommand);
commandFuture.getResponseFuture().whenComplete((result, ex) -> {
sendCommandFutureMap.remove(deviceCommand.getCmdId());
});
return commandFuture;
}
public void executeCommand(CommandFuture commandFuture) {
int cmdId = CyclicNumberGenerator.getInstance().generateNumber();
commandFuture.getDeviceCommand().setCmdId(cmdId);
sendCommandFutureMap.put(cmdId, commandFuture);
commandFuture.setStartSendTime(System.currentTimeMillis());
if (!deviceState.isVirtual()) {
if (!hardwareService.sendCommand(commandFuture.getDeviceCommand())) {
sendCommandFutureMap.remove(commandFuture.getDeviceCommand().getCmdId());
throw new RuntimeException("向设备发送指令失败");
}
if (commandFuture.getCmdId() != null) {
webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_SEND, commandFuture.getDeviceCommand().getDevice() + "_" + commandFuture.getDeviceCommand().getAction() + "指令,已发给设备", commandFuture.getDeviceCommand()));
}
} else {
//虚拟模式
log.info("模拟向设备发送TCP指令:{}", JSONUtil.toJsonStr(commandFuture.getDeviceCommand()));
//模拟反馈
publisher.publishEvent(new VirtualDeviceCmdResponseEvent(this, commandFuture.getDeviceCommand()));
}
}
public CommandFuture sendCommand(DeviceCommand deviceCommand) {
CommandFuture commandFuture = createDeviceCommandFuture(deviceCommand);
executeCommand(commandFuture);
return commandFuture;
}
public CommandFuture sendCommand(String cmdId, String cmdCode, DeviceCommand deviceCommand) {
CommandFuture commandFuture = createDeviceCommandFuture(cmdId, cmdCode, deviceCommand);
executeCommand(commandFuture);
return commandFuture;
}
public void completeCommandResponse(JSONObject deviceResult) {
Integer cmdId = deviceResult.getInt("cmdId");
if (cmdId != null) {
CommandFuture commandFuture = sendCommandFutureMap.get(cmdId);
if (commandFuture != null) {
commandFuture.setEndSendTime(System.currentTimeMillis());
Boolean success = deviceResult.getBool("success"); //数据验证
if (success == null || !success) { //response失败
if (commandFuture.getCmdId() != null) {
webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_ERROR,
commandFuture.getDeviceCommand().getDevice() + "_" + commandFuture.getDeviceCommand().getAction() + "指令,设备response错误,耗时:" + (commandFuture.getEndSendTime() - commandFuture.getStartSendTime()), deviceResult));
}
commandFuture.completeResponseExceptionally(new RuntimeException("response失败:" + deviceResult));
} else {
if (commandFuture.getCmdId() != null) {
webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_RESULT,
commandFuture.getDeviceCommand().getDevice() + "_" + commandFuture.getDeviceCommand().getAction() + "指令,设备response正常,耗时:" + (commandFuture.getEndSendTime() - commandFuture.getStartSendTime()), deviceResult));
}
commandFuture.completeResponse(deviceResult);
}
}
}
}
/**
* 取消等待中的future并从map中移除
*/
public synchronized void releaseAllCommandFutures() {
for (Integer key : sendCommandFutureMap.keySet()) {
CommandFuture future = sendCommandFutureMap.remove(key);
if (future != null) {
future.getResponseFuture().cancel(true);
}
}
}
}

47
src/main/java/com/iflytop/handacid/app/service/DeviceInitService.java

@ -0,0 +1,47 @@
package com.iflytop.handacid.app.service;
import com.iflytop.handacid.app.core.state.DeviceState;
import com.iflytop.handacid.hardware.service.AppEventBusService;
import com.iflytop.handacid.hardware.type.appevent.A8kCanBusOnConnectEvent;
import com.iflytop.handacid.hardware.type.appevent.AppEvent;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceInitService {
private final AppEventBusService eventBus;
private boolean isLink = false;
private final DeviceState deviceState;
@PostConstruct
public void init() {
eventBus.regListener(this::onAppEvent);
new Thread(() -> {
try {
log.info("初始化开始");
initDeviceState();
//缓存点位数据
log.info("初始化完毕");
} catch (Exception e) {
}
}).start();
}
private void onAppEvent(AppEvent event) {
if (event instanceof A8kCanBusOnConnectEvent) {
isLink = true;
}
}
public void initDeviceState() {
log.info("初始化 initDeviceState");
log.info("初始化 initDeviceState完毕");
}
}

119
src/main/java/com/iflytop/handacid/app/service/DeviceParamConfigService.java

@ -0,0 +1,119 @@
package com.iflytop.handacid.app.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.iflytop.handacid.common.mapper.DeviceParamConfigMapper;
import com.iflytop.handacid.common.model.entity.DeviceParamConfig;
import com.iflytop.handacid.common.model.vo.DeviceParamGroupVO;
import com.iflytop.handacid.common.model.vo.ModuleIdVO;
import com.iflytop.handacid.common.model.vo.RegIndexVO;
import com.iflytop.handacid.hardware.type.MId;
import com.iflytop.handacid.hardware.type.RegIndex;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 设备参数配置服务
*/
@Service
@RequiredArgsConstructor
public class DeviceParamConfigService extends ServiceImpl<DeviceParamConfigMapper, DeviceParamConfig> {
private final DeviceParamConfigMapper deviceParamConfigMapper;
/**
* ModuleId 分组列出每个模块下的 regIndex + regVal
*/
public List<DeviceParamGroupVO> listGroupedByModule() {
// 拉取所有配置记录
List<DeviceParamConfig> all = this.list();
// mid 分组
Map<String, List<DeviceParamConfig>> grouped = all.stream()
.collect(Collectors.groupingBy(DeviceParamConfig::getMid));
// 构造 VO 列表
return grouped.entrySet().stream()
.map(entry -> {
String moduleId = entry.getKey();
List<DeviceParamGroupVO.ParamItem> items = entry.getValue().stream()
.map(cfg -> new DeviceParamGroupVO.ParamItem(
cfg.getRegIndex(),
cfg.getRegVal()
))
.collect(Collectors.toList());
return new DeviceParamGroupVO(moduleId, items);
})
.collect(Collectors.toList());
}
/**
* 根据模块标识和寄存器索引查询设备参数配置
*
* @param moduleId 模块枚举
* @param regIndex 寄存器索引
* @return 唯一匹配的 DeviceParamConfig找不到返回 null
*/
public DeviceParamConfig getByModuleAndReg(MId moduleId, RegIndex regIndex) {
return deviceParamConfigMapper.selectOne(
new LambdaQueryWrapper<DeviceParamConfig>()
.eq(DeviceParamConfig::getMid, moduleId.name())
.eq(DeviceParamConfig::getRegIndex, regIndex.name())
);
}
/**
* 根据模块标识和寄存器索引设置设备参数配置
*
* @param moduleId 模块枚举
* @param regIndex 寄存器索引
* @param regVal 寄存器值
* @return 唯一匹配的 DeviceParamConfig找不到返回 null
*/
public int setModuleAndReg(String moduleId, String regIndex, Integer regVal) {
return deviceParamConfigMapper.update(new LambdaUpdateWrapper<DeviceParamConfig>()
.eq(DeviceParamConfig::getMid, moduleId)
.eq(DeviceParamConfig::getRegIndex, regIndex)
.set(DeviceParamConfig::getRegVal, regVal));
}
/**
* 列出所有 ModuleId返回 VO 列表
*
* @return List<ModuleIdVO>
*/
public List<ModuleIdVO> listAllModuleIds() {
return Arrays.stream(MId.values())
.map(e -> new ModuleIdVO(
e.name(), // 枚举常量名
e.description // 描述字段
))
.collect(Collectors.toList());
}
/**
* 列出所有寄存器索引枚举返回 VO 列表
*
* @return List<RegIndexVO>
*/
public List<RegIndexVO> listAllRegIndices() {
return Arrays.stream(RegIndex.values())
.map(e -> new RegIndexVO(
e.name() // 枚举常量名
))
.collect(Collectors.toList());
}
public boolean deleteDeviceParam(String idsStr) {
List<Long> ids = Arrays.stream(idsStr.split(","))
.map(Long::parseLong)
.collect(Collectors.toList());
return this.removeByIds(ids);
}
}

27
src/main/java/com/iflytop/handacid/app/service/SystemService.java

@ -0,0 +1,27 @@
package com.iflytop.handacid.app.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.iflytop.handacid.common.mapper.SystemConfigMapper;
import com.iflytop.handacid.common.model.entity.SystemConfig;
import com.iflytop.handacid.common.utils.CommandUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 系统配置接口服务
*/
@Service
@RequiredArgsConstructor
public class SystemService extends ServiceImpl<SystemConfigMapper, SystemConfig> {
/**
* 将系统时区可选和系统时间必选一起设定
*
* @param epochMilli UTC 毫秒时间戳
*/
public void setSystemTime(long epochMilli) {
long epochSecond = epochMilli / 1_000;
CommandUtil.runCommand("timedatectl", "set-ntp", "false");
CommandUtil.runCommand("date", "-s", "@" + epochSecond);
CommandUtil.runCommand("hwclock", "--systohc");
}
}

15
src/main/java/com/iflytop/handacid/app/service/TestService.java

@ -0,0 +1,15 @@
package com.iflytop.handacid.app.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 测试用
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TestService {
}

45
src/main/java/com/iflytop/handacid/app/service/VirtualDeviceService.java

@ -0,0 +1,45 @@
package com.iflytop.handacid.app.service;
import cn.hutool.json.JSONObject;
import com.iflytop.handacid.app.core.command.DeviceCommand;
import com.iflytop.handacid.common.enums.Action;
import com.iflytop.handacid.common.enums.Device;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 虚拟设备服务
*/
@Service
@RequiredArgsConstructor
public class VirtualDeviceService {
private final DeviceCommandService deviceCommandService;
public void completeCommandResponse(DeviceCommand cmdToDevice) {
new Thread(() -> {
try {
JSONObject jsonObject = new JSONObject();
jsonObject.putOnce("cmdId", cmdToDevice.getCmdId());
jsonObject.putOnce("success", true);
String code = cmdToDevice.getCmdCode();
Action action = cmdToDevice.getAction();
Device device = cmdToDevice.getDevice();
if (code.contains("controlMotorCmd")) {
if (Action.ORIGIN.equals(action)) {
Thread.sleep(3000);
} else if (!Action.SET.equals(action)) {//非设置电机参数也就是电机移动
Thread.sleep(500);
}
} else if (code.contains("getInfoCmd")) {
}
deviceCommandService.completeCommandResponse(jsonObject);
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
}
}).start();
}
}

22
src/main/java/com/iflytop/handacid/app/websocket/server/DebugGenerator.java

@ -0,0 +1,22 @@
package com.iflytop.handacid.app.websocket.server;
import cn.hutool.json.JSONObject;
public class DebugGenerator {
public static JSONObject generateJson(String commandId, String command, String status, String title, Object content) {
JSONObject jsonObject = new JSONObject();
jsonObject.set("commandId", commandId);
jsonObject.set("command", command);
jsonObject.set("status", status);
jsonObject.set("title", title);
jsonObject.set("content", content);
return jsonObject;
}
public static JSONObject generateJson(String commandId, String command, String status, String title) {
return generateJson(commandId, command, status, title, null);
}
}

59
src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketMessageType.java

@ -0,0 +1,59 @@
package com.iflytop.handacid.app.websocket.server;
public class WebSocketMessageType {
/**
* 设备状态
*/
public static final String STATUS = "status";
/**
* 设备报警
*/
public static final String ALARM = "alarm";
/**
* 滴定日志
*/
public static final String LOG = "log";
/**
* 自检移动电机测试
*/
public static final String SELF_MOVE_TEST = "self_move_test";
/**
* 指令反馈
*/
public static final String CMD_RESPONSE = "cmd_response";
/**
* 工艺执行步骤反馈
*/
public static final String CRAFTS_STEP = "crafts_step";
/**
* 工艺执行状态反馈
*/
public static final String CRAFTS_STATE = "crafts_state";
/**
* 工艺DEBUG
*/
public static final String CRAFTS_DEBUG = "crafts_debug";
/**
* 容器剩余状态
*/
public static final String CONTAINER = "container";
/**
* DEBUG消息推送
*/
public static final String CMD_DEBUG = "cmd_debug";
/**
* 加热倒计时
*/
public static final String HEAT_COUNTDOWN = "heat_countdown";
/**
* 照片
*/
public static final String PHOTO = "photo";
}

59
src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketSender.java

@ -0,0 +1,59 @@
package com.iflytop.handacid.app.websocket.server;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@Component
public class WebSocketSender {
public void push(String type, Object data) {
WebsocketResult websocketResult = new WebsocketResult();
websocketResult.setType(type);
websocketResult.setData(data);
websocketResult.setTimestamp(Instant.now().toEpochMilli());
WebSocketServer.sendMessageToClients(JSONUtil.toJsonStr(websocketResult));
// log.info("WS::{}", JSONUtil.toJsonStr(websocketResult));
}
public void pushCraftsDebug(Object data) {
push(WebSocketMessageType.CRAFTS_DEBUG, data);
}
public void pushDebug(Object data) {
push(WebSocketMessageType.CMD_DEBUG, data);
}
public void pushLog(String code, String type, String content) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDate = LocalDateTime.now().format(formatter);
WebsocketResult websocketResult = new WebsocketResult();
websocketResult.setType(WebSocketMessageType.LOG);
websocketResult.setData(formattedDate + "模块" + StringUtils.substring(code, code.length() - 1) + type + content);
websocketResult.setTimestamp(Instant.now().toEpochMilli());
WebSocketServer.sendMessageToClients(JSONUtil.toJsonStr(websocketResult));
}
public void pushCMDResponse(Object data) {
push(WebSocketMessageType.CMD_RESPONSE, data);
}
public void pushSelfMoveTest(Object data) {
push(WebSocketMessageType.SELF_MOVE_TEST, data);
}
public void pushHeatCountdown(Object data) {
push(WebSocketMessageType.HEAT_COUNTDOWN, data);
}
/* public void pushNotification(Notification notification) {
push("notification", notification);
}*/
}

51
src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServer.java

@ -0,0 +1,51 @@
package com.iflytop.handacid.app.websocket.server;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@Slf4j
@ServerEndpoint("/ws")
@Component
public class WebSocketServer {
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
public static void sendMessageToClients(String message) {
synchronized (sessions) {
for (Session session : sessions) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
log.error("WS发送给客户端失败 sessionId={}", session.getId(), e);
}
}
}
}
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
log.info("新连接加入,sessionId={}", session.getId());
}
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到消息 sessionId={},内容:{}", session.getId(), message);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
log.info("连接已关闭,sessionId={}", session.getId());
}
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误,sessionId={}", session.getId(), error);
}
}

14
src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServerConfig.java

@ -0,0 +1,14 @@
package com.iflytop.handacid.app.websocket.server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketServerConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

21
src/main/java/com/iflytop/handacid/app/websocket/server/WebsocketResult.java

@ -0,0 +1,21 @@
package com.iflytop.handacid.app.websocket.server;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class WebsocketResult {
/**
* 推送类型(指令 cmd,报警 warn ,状态 status)
*/
@Schema(description = "推送类型(指令 cmd,报警 warn ,状态 status)")
private String type;
/**
* 执行结果
*/
@Schema(description = "推送数据")
private Object data;
@Schema(description = "推送时间戳")
private Long timestamp;
}

44
src/main/java/com/iflytop/handacid/common/base/BaseEntity.java

@ -0,0 +1,44 @@
package com.iflytop.handacid.common.base;
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 com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体类
* 实体类的基类包含了实体类的公共属性如创建时间更新时间逻辑删除标识等</p>
*/
@Data
public class BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

26
src/main/java/com/iflytop/handacid/common/base/BasePageQuery.java

@ -0,0 +1,26 @@
package com.iflytop.handacid.common.base;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 基础分页请求对象
*/
@Data
@Schema
public class BasePageQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "页码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private int pageNum = 1;
@Schema(description = "每页记录数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private int pageSize = 10;
}

97
src/main/java/com/iflytop/handacid/common/base/IBaseEnum.java

@ -0,0 +1,97 @@
package com.iflytop.handacid.common.base;
import cn.hutool.core.util.ObjectUtil;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Objects;
/**
* 枚举通用接口
*/
public interface IBaseEnum<T> {
/**
* 根据值获取枚举
*
* @param value
* @param clazz
* @param <E> 枚举
* @return
*/
static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getValue(), value))
.findFirst()
.orElse(null);
return matchEnum;
}
/**
* 根据文本标签获取值
*
* @param value
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getValue(), value))
.findFirst()
.orElse(null);
String label = null;
if (matchEnum != null) {
label = matchEnum.getLabel();
}
return label;
}
/**
* 根据文本标签获取值
*
* @param label
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
Objects.requireNonNull(label);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
String finalLabel = label;
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel))
.findFirst()
.orElse(null);
Object value = null;
if (matchEnum != null) {
value = matchEnum.getValue();
}
return value;
}
/**
* 根据name值比对是否包含
*
* @param enumClass
* @param name
* @return
*/
static <T extends Enum<T>> boolean contains(Class<T> enumClass, String name) {
return Arrays.stream(enumClass.getEnumConstants())
.map(Enum::name)
.anyMatch(name::equals);
}
T getValue();
String getLabel();
}

24
src/main/java/com/iflytop/handacid/common/command/DeviceCommandParams.java

@ -0,0 +1,24 @@
package com.iflytop.handacid.common.command;
import com.iflytop.handacid.common.enums.BeepMode;
import com.iflytop.handacid.common.enums.MotorDirection;
import com.iflytop.handacid.common.enums.TricolorLightColor;
import lombok.Data;
@Data
public class DeviceCommandParams {
private String device;
private MotorDirection direction;
private Double position;
private Double speed;
private Double angle;
private Double volume;
private Double distance;
private Double temperature;
private TricolorLightColor color;
private BeepMode mode;
private Double x;
private Double y;
private Double z;
}

32
src/main/java/com/iflytop/handacid/common/config/A8kCanBusConnectionConfig.java

@ -0,0 +1,32 @@
package com.iflytop.handacid.common.config;
import com.iflytop.handacid.hardware.comm.can.A8kCanBusConnection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class A8kCanBusConnectionConfig {
//协议参考 https://iflytop1.feishu.cn/wiki/QFwVwGnI8iYp0fk20W9cnpYAnkg
@Value("${iflytophald.ip}")
String ip;
@Value("${iflytophald.cmdch.port}")
Integer cmdchPort;
@Value("${iflytophald.datach.port}")
Integer datachPort;
public String getDatachUrl(String datachannel) {
return String.format("ws://%s:%d/%s", ip, datachPort, datachannel);
}
public String getCmdChBaseUrl(String cmdchannel) {
return String.format("http://%s:%d/%s", ip, cmdchPort, cmdchannel);
}
@Bean
A8kCanBusConnection a8kCanBusBaseService() {
return new A8kCanBusConnection(getCmdChBaseUrl("zexcan"), getDatachUrl("zexcan"));
}
}

61
src/main/java/com/iflytop/handacid/common/config/MybatisPlusConfig.java

@ -0,0 +1,61 @@
package com.iflytop.handacid.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.iflytop.handacid.app.common.handler.MyMetaObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
*
*/
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
return interceptor;
}
/**
* 分页插件自动识别数据库类型
* <a href="https://baomidou.com/guide/interceptor-pagination.html">...</a>
*/
public PaginationInnerInterceptor paginationInnerInterceptor() {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置数据库类型
paginationInnerInterceptor.setDbType(DbType.SQLITE);
// 设置最大单页限制数量默认 500 -1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 乐观锁插件
* <a href="https://baomidou.com/guide/interceptor-optimistic-locker.html">...</a>
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
/**
* 自动填充数据库创建人创建时间更新人更新时间
*/
@Bean
public GlobalConfig globalConfig() {
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
return globalConfig;
}
}

68
src/main/java/com/iflytop/handacid/common/config/SwaggerConfig.java

@ -0,0 +1,68 @@
package com.iflytop.handacid.common.config;
import cn.hutool.core.util.ArrayUtil;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.util.AntPathMatcher;
import java.util.stream.Stream;
/***
* 创建Swagger配置
*/
@Configuration
public class SwaggerConfig {
@Bean
public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {
return openApi -> {
// 全局添加Authorization
if (openApi.getPaths() != null) {
openApi.getPaths().forEach((path, pathItem) -> {
// 忽略认证的请求无需携带 Authorization
String[] ignoreUrls = {"/api/auth/login"};
if (ArrayUtil.isNotEmpty(ignoreUrls)) {
// Ant 匹配忽略的路径不添加Authorization
AntPathMatcher antPathMatcher = new AntPathMatcher();
if (Stream.of(ignoreUrls).anyMatch(ignoreUrl -> antPathMatcher.match(ignoreUrl, path))) {
return;
}
}
// 其他接口统一添加Authorization
// pathItem.readOperations()
// .forEach(operation ->
// operation.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION))
// );
});
}
};
}
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("系统API")
.version("1.0")) // 配置全局鉴权参数-Authorize
.components(new Components()
.addSecuritySchemes(HttpHeaders.AUTHORIZATION,
new SecurityScheme()
.name(HttpHeaders.AUTHORIZATION)
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.scheme("Bearer")
.bearerFormat("JWT")
)
);
}
}

16
src/main/java/com/iflytop/handacid/common/config/WebConfig.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.common.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
}

39
src/main/java/com/iflytop/handacid/common/enums/Action.java

@ -0,0 +1,39 @@
package com.iflytop.handacid.common.enums;
import lombok.Getter;
/**
* 通用设备动作枚举
*/
public enum Action {
MOVE("绝对移动"),
MOVE_BY("相对移动"),
ORIGIN("回原点"),
ROTATE("旋转"),
STOP("停止"),
OPEN("打开"),
CLOSE("关闭"),
SET("设置"),
GET("获取"),
OPEN_CLAMP("打开抱闸"),
CLOSE_CLAMP("关闭抱闸"),
ENABLE("使能"),
DISABLE("失能"),
MOVE_END("移动到末端点"),
;
/**
* 动作的中文描述
*/
@Getter
private final String description;
Action(String description) {
this.description = description;
}
@Override
public String toString() {
return name();
}
}

19
src/main/java/com/iflytop/handacid/common/enums/BeepMode.java

@ -0,0 +1,19 @@
package com.iflytop.handacid.common.enums;
import com.iflytop.handacid.common.base.IBaseEnum;
public enum BeepMode implements IBaseEnum {
alarm, // 报警音
info; //提示音
@Override
public Object getValue() {
return null;
}
@Override
public String getLabel() {
return "";
}
}

66
src/main/java/com/iflytop/handacid/common/enums/Device.java

@ -0,0 +1,66 @@
package com.iflytop.handacid.common.enums;
import lombok.Getter;
public enum Device {
Z_MOTOR(HardwareType.STEPPER_MOTOR, "Z 轴升降电机"),
CERAMIC_PUMP_1(HardwareType.STEPPER_MOTOR, "陶瓷泵 1"),
CERAMIC_PUMP_2(HardwareType.STEPPER_MOTOR, "陶瓷泵 2"),
BRUSHLESS_PUMP_1(HardwareType.STEPPER_MOTOR, "无刷泵 1"),
BRUSHLESS_PUMP_2(HardwareType.STEPPER_MOTOR, "无刷泵 2"),
BRUSHLESS_PUMP_3(HardwareType.STEPPER_MOTOR, "无刷泵 3"),
BRUSHLESS_PUMP_4(HardwareType.STEPPER_MOTOR, "无刷泵 4"),
BRUSHLESS_PUMP_5(HardwareType.STEPPER_MOTOR, "无刷泵 5"),
BRUSHLESS_PUMP_6(HardwareType.STEPPER_MOTOR, "无刷泵 6"),
BRUSHLESS_PUMP_7(HardwareType.STEPPER_MOTOR, "无刷泵 7"),
BRUSHLESS_PUMP_8(HardwareType.STEPPER_MOTOR, "无刷泵 8"),
BRUSHLESS_PUMP_9(HardwareType.STEPPER_MOTOR, "无刷泵 9"),
BRUSHLESS_PUMP_10(HardwareType.STEPPER_MOTOR, "无刷泵 10"),
STEP_PUMP_1(HardwareType.STEPPER_MOTOR, "步进泵 1"),
STEP_PUMP_2(HardwareType.STEPPER_MOTOR, "步进泵 2"),
STEP_PUMP_3(HardwareType.STEPPER_MOTOR, "步进泵 3"),
STIR_MOTOR_1(HardwareType.STEPPER_MOTOR, "搅拌电机 1"),
STIR_MOTOR_2(HardwareType.STEPPER_MOTOR, "搅拌电机 2"),
TITRATION_MOTOR_1(HardwareType.STEPPER_MOTOR, "滴定移动电机 1"),
TITRATION_MOTOR_2(HardwareType.STEPPER_MOTOR, "滴定移动电机 2"),
CLAW(HardwareType.SERVO_MOTOR, "夹爪"),
ROBOTIC_ARM_BIG(HardwareType.STEPPER_MOTOR, "双轴机械臂大臂"),
ROBOTIC_ARM_SMALL(HardwareType.STEPPER_MOTOR, "双轴机械臂小臂"),
HEAT_ROD_1(HardwareType.IO_DEVICE, "加热棒 1"),
HEAT_ROD_2(HardwareType.IO_DEVICE, "加热棒 2"),
TRICOLOR_LIGHT(HardwareType.IO_DEVICE, "三色灯"),
BEEP(HardwareType.IO_DEVICE, "蜂鸣器"),
MAGNET(HardwareType.IO_DEVICE, "电磁开关"),
CONTAINER_LOW_LEVEL_1(HardwareType.IO_DEVICE, "容器低液位检测 1"),
CONTAINER_LOW_LEVEL_2(HardwareType.IO_DEVICE, "容器低液位检测 2"),
CONTAINER_LOW_LEVEL_3(HardwareType.IO_DEVICE, "容器低液位检测 3"),
CONTAINER_LOW_LEVEL_4(HardwareType.IO_DEVICE, "容器低液位检测 4"),
CONTAINER_LOW_LEVEL_5(HardwareType.IO_DEVICE, "容器低液位检测 5"),
CONTAINER_LOW_LEVEL_6(HardwareType.IO_DEVICE, "容器低液位检测 6"),
CONTAINER_LOW_LEVEL_7(HardwareType.IO_DEVICE, "容器低液位检测 7"),
CONTAINER_LOW_LEVEL_8(HardwareType.IO_DEVICE, "容器低液位检测 8"),
HEATER_TUBE_EXIST_1(HardwareType.IO_DEVICE, "加热位是否存在试管 1"),
HEATER_TUBE_EXIST_2(HardwareType.IO_DEVICE, "加热位是否存在试管 2"),
;
/**
* 设备所属硬件类型
*/
@Getter
private final HardwareType type;
/**
* 设备中文描述或显示名
*/
@Getter
private final String description;
Device(HardwareType type, String description) {
this.type = type;
this.description = description;
}
@Override
public String toString() {
return this.name();
}
}

5
src/main/java/com/iflytop/handacid/common/enums/EnableStatus.java

@ -0,0 +1,5 @@
package com.iflytop.handacid.common.enums;
public enum EnableStatus {
ENABLE, DISABLE
}

25
src/main/java/com/iflytop/handacid/common/enums/HardwareType.java

@ -0,0 +1,25 @@
package com.iflytop.handacid.common.enums;
public enum HardwareType {
/**
* 步进电机
*/
STEPPER_MOTOR,
/**
* 伺服电机
*/
SERVO_MOTOR,
/**
* 通用传感器温度湿度等
*/
SENSOR,
/**
* IO 控制阀门风扇加热器等
*/
IO_DEVICE,
/**
* 相机
*/
CAMERA;
}

5
src/main/java/com/iflytop/handacid/common/enums/MotorDirection.java

@ -0,0 +1,5 @@
package com.iflytop.handacid.common.enums;
public enum MotorDirection {
FORWARD, BACKWARD
}

17
src/main/java/com/iflytop/handacid/common/enums/TricolorLightColor.java

@ -0,0 +1,17 @@
package com.iflytop.handacid.common.enums;
import com.iflytop.handacid.common.base.IBaseEnum;
public enum TricolorLightColor implements IBaseEnum {
RED, GREEN, BLUE;
@Override
public Object getValue() {
return null;
}
@Override
public String getLabel() {
return "";
}
}

21
src/main/java/com/iflytop/handacid/common/exception/AppException.java

@ -0,0 +1,21 @@
package com.iflytop.handacid.common.exception;
import com.iflytop.handacid.common.result.IResultCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class AppException extends RuntimeException {
private final IResultCode resultCode;
public AppException(IResultCode resultCode) {
super(resultCode.getMsg());
this.resultCode = resultCode;
}
@Override
public String toString() {
return "AppException{" + "code='" + resultCode.getCode() + ", msg=" + resultCode.getMsg() + '}';
}
}

12
src/main/java/com/iflytop/handacid/common/mapper/DeviceParamConfigMapper.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.iflytop.handacid.common.model.entity.DeviceParamConfig;
import org.apache.ibatis.annotations.Mapper;
/**
* 设备参数配置持久层接口
*/
@Mapper
public interface DeviceParamConfigMapper extends BaseMapper<DeviceParamConfig> {
}

13
src/main/java/com/iflytop/handacid/common/mapper/SolutionsMapper.java

@ -0,0 +1,13 @@
package com.iflytop.handacid.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.iflytop.handacid.common.model.entity.Solutions;
import org.apache.ibatis.annotations.Mapper;
/**
* 溶液持久层接口
*/
@Mapper
public interface SolutionsMapper extends BaseMapper<Solutions> {
}

12
src/main/java/com/iflytop/handacid/common/mapper/SystemConfigMapper.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.iflytop.handacid.common.model.entity.SystemConfig;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统配置持久层接口
*/
@Mapper
public interface SystemConfigMapper extends BaseMapper<SystemConfig> {
}

12
src/main/java/com/iflytop/handacid/common/mapper/SystemLogMapper.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.iflytop.handacid.common.model.entity.SystemLog;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统日志持久层接口
*/
@Mapper
public interface SystemLogMapper extends BaseMapper<SystemLog> {
}

12
src/main/java/com/iflytop/handacid/common/mapper/UserMapper.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.iflytop.handacid.common.model.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户持久层接口
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

20
src/main/java/com/iflytop/handacid/common/model/bo/Point2D.java

@ -0,0 +1,20 @@
package com.iflytop.handacid.common.model.bo;
import lombok.Data;
@Data
public class Point2D {
public Double x;
public Double y;
public Point2D() {
}
public Point2D(Double x, Double y) {
this.x = x;
this.y = y;
}
}

22
src/main/java/com/iflytop/handacid/common/model/bo/Point3D.java

@ -0,0 +1,22 @@
package com.iflytop.handacid.common.model.bo;
import lombok.Data;
@Data
public class Point3D {
public Double x;
public Double y;
public Double z;
public Point3D() {
}
public Point3D(Double x, Double y, Double z) {
this.x = x;
this.y = y;
this.z = z;
}
}

30
src/main/java/com/iflytop/handacid/common/model/entity/DeviceParamConfig.java

@ -0,0 +1,30 @@
package com.iflytop.handacid.common.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iflytop.handacid.common.base.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Schema(description = "设备参数配置")
@TableName("device_param_config")
@Data
public class DeviceParamConfig extends BaseEntity {
@NotNull
@NotBlank
@Schema(description = "模块标识")
private String mid;
@NotNull
@NotBlank
@Schema(description = "寄存器索引")
private String regIndex;
@Schema(description = "寄存器值")
private Integer regVal;
}

17
src/main/java/com/iflytop/handacid/common/model/entity/Solutions.java

@ -0,0 +1,17 @@
package com.iflytop.handacid.common.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iflytop.handacid.common.base.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@TableName("solutions")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "溶液")
public class Solutions extends BaseEntity {
@Schema(description = "溶液名称")
private String name;
}

21
src/main/java/com/iflytop/handacid/common/model/entity/SystemConfig.java

@ -0,0 +1,21 @@
package com.iflytop.handacid.common.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iflytop.handacid.common.base.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@TableName("system_config")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统配置")
public class SystemConfig extends BaseEntity {
@Schema(description = "配置键")
private String key;
@Schema(description = "配置值")
private String value;
}

20
src/main/java/com/iflytop/handacid/common/model/entity/SystemLog.java

@ -0,0 +1,20 @@
package com.iflytop.handacid.common.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iflytop.handacid.common.base.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@TableName("system_log")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "系统日志")
public class SystemLog extends BaseEntity {
@Schema(description = "日志标题")
private String title;
@Schema(description = "日志内容")
private String content;
}

44
src/main/java/com/iflytop/handacid/common/model/entity/User.java

@ -0,0 +1,44 @@
package com.iflytop.handacid.common.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.iflytop.handacid.common.base.BaseEntity;
import com.iflytop.handacid.common.enums.EnableStatus;
import com.iflytop.handacid.app.common.enums.UserRole;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 用户实体
*/
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户")
@TableName("user")
@Data
public class User extends BaseEntity {
@NotNull
@Schema(description = "用户名")
private String username;
@NotNull
@Schema(description = "昵称")
private String nickname;
@NotNull
@Schema(description = "密码")
private String password;
@NotNull
@Schema(description = "人员角色")
private UserRole role;
@NotNull
@Schema(description = "是否删除")
private EnableStatus deleted;
@Schema(description = "是否是系统固定用户")
private EnableStatus fixedUser;
}

36
src/main/java/com/iflytop/handacid/common/model/vo/DeviceParamGroupVO.java

@ -0,0 +1,36 @@
package com.iflytop.handacid.common.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* 按模块分组的设备参数视图
*/
@Data
@Schema(description = "按模块分组的设备参数")
public class DeviceParamGroupVO {
@Schema(description = "模块标识(ModuleId.name),如 DoorM")
private String mid;
@Schema(description = "该模块下的所有配置项")
private List<ParamItem> reg;
public DeviceParamGroupVO(String mid, List<ParamItem> reg) {
this.mid = mid;
this.reg = reg;
}
@AllArgsConstructor
@Data
@Schema(description = "配置项")
public static class ParamItem {
@Schema(description = "配置项名称,如 kreg_step_motor_pos")
private String name;
@Schema(description = "值")
private Integer value;
}
}

19
src/main/java/com/iflytop/handacid/common/model/vo/ModuleIdVO.java

@ -0,0 +1,19 @@
package com.iflytop.handacid.common.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* ModuleId 的展示视图
*/
@Data
@AllArgsConstructor
@Schema(description = "ModuleId 的展示视图")
public class ModuleIdVO {
@Schema(description = "模块名称,如 DoorM")
private String name;
@Schema(description = "模块描述")
private String description;
}

16
src/main/java/com/iflytop/handacid/common/model/vo/RegIndexVO.java

@ -0,0 +1,16 @@
package com.iflytop.handacid.common.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* RegIndex 的展示视图
*/
@Data
@AllArgsConstructor
@Schema(description = "RegIndex 的展示视图")
public class RegIndexVO {
@Schema(description = "配置项名称,如 kreg_step_motor_pos")
private String name;
}

12
src/main/java/com/iflytop/handacid/common/result/IResultCode.java

@ -0,0 +1,12 @@
package com.iflytop.handacid.common.result;
/**
* 响应码接口
**/
public interface IResultCode {
String getCode();
String getMsg();
}

43
src/main/java/com/iflytop/handacid/common/result/PageResult.java

@ -0,0 +1,43 @@
package com.iflytop.handacid.common.result;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页响应结构体
*/
@Data
public class PageResult<T> implements Serializable {
private String code;
private Data<T> data;
private String msg;
public static <T> PageResult<T> success(IPage<T> page) {
PageResult<T> result = new PageResult<>();
result.setCode(ResultCode.SUCCESS.getCode());
Data<T> data = new Data<>();
data.setList(page.getRecords());
data.setTotal(page.getTotal());
result.setData(data);
result.setMsg(ResultCode.SUCCESS.getMsg());
return result;
}
@lombok.Data
public static class Data<T> {
private List<T> list;
private long total;
}
}

75
src/main/java/com/iflytop/handacid/common/result/Result.java

@ -0,0 +1,75 @@
package com.iflytop.handacid.common.result;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import java.io.Serializable;
/**
* 统一响应结构体
**/
@Data
public class Result<T> implements Serializable {
private String code;
private T data;
private String msg;
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMsg(ResultCode.SUCCESS.getMsg());
result.setData(data);
return result;
}
public static <T> Result<T> failed() {
return result(ResultCode.SYSTEM_ERROR.getCode(), ResultCode.SYSTEM_ERROR.getMsg(), null);
}
public static <T> Result<T> failed(String msg) {
return result(ResultCode.SYSTEM_ERROR.getCode(), msg, null);
}
public static <T> Result<T> judge(boolean status) {
if (status) {
return success();
} else {
return failed();
}
}
public static <T> Result<T> failed(IResultCode resultCode) {
return result(resultCode.getCode(), resultCode.getMsg(), null);
}
public static <T> Result<T> failed(IResultCode resultCode, String msg) {
return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null);
}
public static <T> Result<T> failed(String code, String msg) {
return result(code, msg, null);
}
private static <T> Result<T> result(IResultCode resultCode, T data) {
return result(resultCode.getCode(), resultCode.getMsg(), data);
}
private static <T> Result<T> result(String code, String msg, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setData(data);
result.setMsg(msg);
return result;
}
public static boolean isSuccess(Result<?> result) {
return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode());
}
}

96
src/main/java/com/iflytop/handacid/common/result/ResultCode.java

@ -0,0 +1,96 @@
package com.iflytop.handacid.common.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
/**
* 统一前端响应码定义
*/
@Getter
@AllArgsConstructor
public enum ResultCode implements IResultCode, Serializable {
//================================ 通用 =================================
SUCCESS("0", "成功"),
FAILED("-1", "未知错误"),
//============================ 1xxx请求 & 参数 ============================
INVALID_PARAMETER("1000", "参数无效或缺失"),
PARAMETER_TYPE_MISMATCH("1001", "参数类型不匹配"),
PARAMETER_OUT_OF_RANGE("1002", "参数超出允许范围"),
//============================ 2xxx认证 & 授权 ============================
UNAUTHORIZED("2000", "未认证或登录失效"),
FORBIDDEN("2001", "无访问权限"),
TOKEN_EXPIRED("2002", "Token 已过期"),
TOKEN_INVALID("2003", "Token 无效"),
//============================ 3xxx资源访问 ============================
NOT_FOUND("3000", "资源不存在"),
METHOD_NOT_ALLOWED("3001", "不支持的请求方法"),
//============================ 4xxx业务错误 ============================
USER_NOT_FOUND("4000", "用户不存在"),
USER_ALREADY_EXISTS("4001", "用户已存在"),
INVALID_CREDENTIALS("4002", "用户名或密码错误"),
OPERATION_NOT_ALLOWED("4003", "业务操作不允许"),
DATA_ALREADY_EXISTS("4004", "数据已存在"),
DATA_ALREADY_NOT_EXISTS("4005", "数据不存在"),
CRAFT_RUNNING("4101", "工艺正在执行"),
CRAFT_CONTEXT_NULL("4102", "请先配置该加热区工艺"),
CRAFT_NO_TRAY("4005", "工艺未找到托盘"),
CONTAINER_NOT_FOUND("4201", "未找到对应溶液容器"),
//============================ 5xxx系统 & 第三方 ============================
SYSTEM_ERROR("5000", "系统内部错误"),
SERVICE_UNAVAILABLE("5001", "服务暂不可用"),
EXTERNAL_API_ERROR("5002", "第三方服务调用失败"),
COMMAND_EXEC_TIMEOUT("5003", "命令执行超时"),
HARDWARE_ERROR("5004", "硬件错误"),
EMERGENCY_STOP("5555", "设备急停中"),
//============================ 6xxx设备指令相关 ============================
COMMAND_NOT_FOUND("6000", "指令未找到"),
COMMAND_ALREADY_EXECUTING("6001", "指令正在执行,无法重复执行"),
SENSOR_STATUS_FAILED("6010", "获取传感器状态失败"),
TARGET_MODULE_OCCUPIED("6021", "目标模块被占用"),
TARGET_MODULE_NO_TUBE("6022", "目标模块无试管"),
CMD_BUSY("6025", "设备忙,请稍后"),
;
/**
* 状态码
*/
private final String code;
/**
* 提示信息
*/
private final String msg;
@Override
public String getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
/**
* 根据 code 获取枚举
*/
public static ResultCode parse(String code) {
for (ResultCode item : values()) {
if (item.code.equals(code)) {
return item;
}
}
return FAILED;
}
@Override
public String toString() {
return "{\"code\":\"" + code + "\", \"msg\":\"" + msg + "\"}";
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save