commit e66b4fb6703f00bf1703037c7d7225ffba5f88e9
Author: guoapeng
Date: Sat Mar 1 14:08:14 2025 +0800
feat: init
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8af972c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+/gradlew text eol=lf
+*.bat text eol=crlf
+*.jar binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f6817ac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+.gradle
+build/
+logs/
+!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/
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..ff576ae
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,58 @@
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '3.3.8'
+ id 'io.spring.dependency-management' version '1.1.7'
+}
+
+group = 'com.qyft'
+version = '0.0.1'
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
+
+configurations {
+ compileOnly {
+ extendsFrom annotationProcessor
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.5.16'
+ implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.48.0.0'
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4'
+ implementation group: 'org.freemarker', name: 'freemarker', version: '2.3.34'
+ implementation group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.10.1'
+ implementation group: 'com.baomidou', name: 'mybatis-plus-jsqlparser', version: '3.5.10.1'
+ implementation group: 'com.baomidou', name: 'mybatis-plus-generator', version: '3.5.10.1'
+ implementation group: 'cn.hutool', name: 'hutool-all', version: '5.8.35'
+ implementation group: 'com.github.xiaoymin', name: 'knife4j-openapi3-jakarta-spring-boot-starter', version: '4.5.0'
+ implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.12.6'
+ runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.12.6'
+ runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.12.6'
+ implementation group: 'com.alibaba', name: 'fastjson', version: '2.0.54'
+ implementation group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '3.4.2'
+ implementation group: 'io.netty', name: 'netty-all', version: '4.1.118.Final'
+
+ //++++++++项目级别的放到下面++++++++
+
+ //++++++++项目级别的放到上面++++++++
+
+ compileOnly 'org.projectlombok:lombok'
+ developmentOnly 'org.springframework.boot:spring-boot-devtools'
+ annotationProcessor 'org.projectlombok:lombok'
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+ testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..a4b76b9
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e18bc25
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..f5feea6
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,252 @@
+#!/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
+' "$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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# 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, 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" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# 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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..9d21a21
--- /dev/null
+++ b/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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+: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
diff --git a/matrix-spray.db b/matrix-spray.db
new file mode 100644
index 0000000..d5c54e7
Binary files /dev/null and b/matrix-spray.db differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..18c7181
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'matrix-spray'
diff --git a/sql/init.sql b/sql/init.sql
new file mode 100644
index 0000000..3e37c76
--- /dev/null
+++ b/sql/init.sql
@@ -0,0 +1,95 @@
+-- 创建 sys_user 表
+CREATE TABLE IF NOT EXISTS sys_user
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL,
+ nickname TEXT,
+ password TEXT NOT NULL,
+ role_id INTEGER,
+ is_deleted TINYINT DEFAULT 0,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 插入测试数据
+INSERT INTO sys_user (username, nickname, password, role_id, is_deleted)
+VALUES ('admin', 'Admin', '12345', 1, 0),
+ ('john_doe', 'John Doe', 'password123', 2, 0),
+ ('test', 'test', 'test123', 3, 0);
+
+
+-- 创建 sys_role 表
+CREATE TABLE IF NOT EXISTS sys_role
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ code TEXT NOT NULL,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 插入角色数据
+INSERT INTO sys_role (name, code)
+VALUES ('管理员', 'ADMIN'),
+ ('普通用户', 'USER'),
+ ('测试用户', 'TEST');
+
+-- 创建 matrix 基质类型表
+CREATE TABLE IF NOT EXISTS matrix
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 创建 matrix_craft 基质工艺表
+CREATE TABLE IF NOT EXISTS matrix_craft
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ matrix_id INTEGER NOT NULL,
+ route_type INTEGER,
+ z_height INTEGER,
+ nitrogen_flow_velocity INTEGER,
+ nitrogen_air_pressure INTEGER,
+ matrix_flow_velocity INTEGER,
+ voltage INTEGER,
+ movement_speed INTEGER,
+ space INTEGER,
+ create_user INTEGER,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 创建 syringe 注射器表
+CREATE TABLE IF NOT EXISTS syringe
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ flow_rate INTEGER,
+ fluid_volume INTEGER,
+ run_time INTEGER,
+ cycle_intervals INTEGER
+);
+
+-- 创建 operation_log 操作记录表
+CREATE TABLE IF NOT EXISTS operation_log
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ matrix_craft_id INTEGER,
+ matrix_info TEXT,
+ status INTEGER,
+ create_user INTEGER,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 创建 sys_settings 系统配置表
+CREATE TABLE IF NOT EXISTS sys_settings
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ parent_id INTEGER,
+ name TEXT NOT NULL,
+ code TEXT,
+ value TEXT
+);
diff --git a/src/main/java/com/qyft/ms/MatrixSprayApplication.java b/src/main/java/com/qyft/ms/MatrixSprayApplication.java
new file mode 100644
index 0000000..4f3265a
--- /dev/null
+++ b/src/main/java/com/qyft/ms/MatrixSprayApplication.java
@@ -0,0 +1,15 @@
+package com.qyft.ms;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+
+@SpringBootApplication
+@ConfigurationPropertiesScan
+public class MatrixSprayApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MatrixSprayApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/common/constant/Commands.java b/src/main/java/com/qyft/ms/common/constant/Commands.java
new file mode 100644
index 0000000..2391464
--- /dev/null
+++ b/src/main/java/com/qyft/ms/common/constant/Commands.java
@@ -0,0 +1,117 @@
+package com.qyft.ms.common.constant;
+
+/**
+ * 操作指令
+ */
+public class Commands {
+ /**
+ * 抬起托盘
+ */
+ public static final String UP_TRAY = "upTray";
+
+ /**
+ * 降下托盘
+ */
+ public static final String DOWN_TRAY = "downTray";
+
+ /**
+ * 添加溶液
+ */
+ public static final String INJECT_FLUID = "injectFluid";
+
+ /**
+ * 恒温
+ */
+ public static final String KEEP_HEAT = "keepHeat";
+
+ /**
+ * 移至加液
+ */
+ public static final String MOVE_TO_ACTION_AREA = "moveToActionArea";
+
+ /**
+ * 检查加液位状态(是否被占用)
+ */
+ public static final String CHECK_ACTION_AREA = "checkActionArea";
+
+ /**
+ * 摇匀试管架
+ */
+ public static final String START_SHAKE_UP = "startShakeUp";
+
+ /**
+ * 停止摇匀试管架
+ */
+ public static final String STOP_SHAKE_UP = "stopShakeUp";
+
+ /**
+ * 开始加热
+ */
+ public static final String START_HEAT = "startHeat";
+
+ /**
+ * 停止加热
+ */
+ public static final String STOP_HEAT = "stopHeat";
+
+ /**
+ * 拍照
+ */
+ public static final String TAKE_PHOTO = "takePhoto";
+
+ /**
+ * 移至异常区
+ */
+ public static final String MOVE_TO_UNUSUAL = "moveToUnusual";
+
+ /**
+ * 从异常区移回加热区
+ */
+ public static final String MOVE_TO_HEAT_AREA = "moveToHeatArea";
+
+ /**
+ * 取试管架盖
+ */
+ public static final String TAKE_OFF_CAP = "takeOffCap";
+
+ /**
+ * 装回试管架盖
+ */
+ public static final String PUT_BACK_CAP = "putBackCap";
+
+ /**
+ * 机械臂移动至指定坐标(x, y, z)
+ */
+ public static final String MOVE_MACHINE_ARM = "moveMachineArm";
+
+ /**
+ * 获取当前某种溶液的数量
+ */
+ public static final String GET_LIQUID_AMOUNT = "getLiquidAmount";
+
+ /**
+ * 移动单个试管
+ */
+ public static final String MOVE_TUBE = "moveTube";
+
+ /**
+ * 机械臂爪子开启
+ */
+ public static final String OPEN_CLAW = "openClaw";
+
+ /**
+ * 机械臂爪子闭合
+ */
+ public static final String CLOSE_CLAW = "closeClaw";
+
+ /**
+ * 开门
+ */
+ public static final String OPEN_DOOR = "openDoor";
+
+ /**
+ * 关门
+ */
+ public static final String CLOSE_DOOR = "closeDoor";
+
+}
diff --git a/src/main/java/com/qyft/ms/common/constant/SysSettings.java b/src/main/java/com/qyft/ms/common/constant/SysSettings.java
new file mode 100644
index 0000000..63c3be3
--- /dev/null
+++ b/src/main/java/com/qyft/ms/common/constant/SysSettings.java
@@ -0,0 +1,27 @@
+package com.qyft.ms.common.constant;
+
+/**
+ * 操作指令
+ */
+public class SysSettings {
+ /**
+ * 加热区域
+ */
+ public static final String HEAT_AREA = "heat_area";
+
+ /**
+ * 加液区域
+ */
+ public static final String SOLUTION_AREA = "solution_area";
+
+ /**
+ * 拍子区域
+ */
+ public static final String LID_AREA = "lid_area";
+
+ /**
+ * 其他系统配置
+ */
+ public static final String SYS_SETTING = "sys_setting";
+
+}
diff --git a/src/main/java/com/qyft/ms/common/constant/WebSocketMessageType.java b/src/main/java/com/qyft/ms/common/constant/WebSocketMessageType.java
new file mode 100644
index 0000000..d3835e5
--- /dev/null
+++ b/src/main/java/com/qyft/ms/common/constant/WebSocketMessageType.java
@@ -0,0 +1,25 @@
+package com.qyft.ms.common.constant;
+
+public class WebSocketMessageType {
+ /**
+ * 设备状态
+ */
+ public static final String STATUS = "status";
+ /**
+ * 设备报警
+ */
+ public static final String ALARM = "alarm";
+ /**
+ * 设备指令反馈
+ */
+ public static final String CMD = "cmd";
+ /**
+ * 工艺执行反馈
+ */
+ public static final String CRAFTS = "crafts";
+ /**
+ * 容器剩余状态
+ */
+ public static final String CONTAINER = "container";
+
+}
diff --git a/src/main/java/com/qyft/ms/common/result/CMDResultCode.java b/src/main/java/com/qyft/ms/common/result/CMDResultCode.java
new file mode 100644
index 0000000..cbf3392
--- /dev/null
+++ b/src/main/java/com/qyft/ms/common/result/CMDResultCode.java
@@ -0,0 +1,27 @@
+package com.qyft.ms.common.result;
+
+import com.qyft.ms.system.common.result.IResultCode;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@AllArgsConstructor
+@NoArgsConstructor
+public enum CMDResultCode implements IResultCode, Serializable {
+ SUCCESS("D0000", "执行完毕"),
+ FAILURE("D1111", "执行失败");
+
+ private String code;
+ private String msg;
+
+ @Override
+ public String getCode() {
+ return code;
+ }
+
+ @Override
+ public String getMsg() {
+ return msg;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/config/WebSocketConfig.java b/src/main/java/com/qyft/ms/config/WebSocketConfig.java
new file mode 100644
index 0000000..cedfe03
--- /dev/null
+++ b/src/main/java/com/qyft/ms/config/WebSocketConfig.java
@@ -0,0 +1,14 @@
+package com.qyft.ms.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/src/main/java/com/qyft/ms/config/WebSocketServer.java b/src/main/java/com/qyft/ms/config/WebSocketServer.java
new file mode 100644
index 0000000..1dc430f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/config/WebSocketServer.java
@@ -0,0 +1,59 @@
+package com.qyft.ms.config;
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qyft.ms.model.dto.WebsocketDTO;
+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 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) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ @OnOpen
+ public void onOpen(Session session) {
+ sessions.add(session);
+ }
+
+ @OnMessage
+ public void onMessage(String message, Session session) {
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ WebsocketDTO c = objectMapper.readValue(message, WebsocketDTO.class);
+ log.info("解析后的信息: commandName={}, commandId={}", c.getCommandName(), c.getCommandId());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @OnClose
+ public void onClose(Session session) {
+ sessions.remove(session); // 移除关闭连接的 Session
+ }
+
+ @OnError
+ public void onError(Throwable error) {
+ }
+}
diff --git a/src/main/java/com/qyft/ms/controller/CMDController.java b/src/main/java/com/qyft/ms/controller/CMDController.java
new file mode 100644
index 0000000..29181f1
--- /dev/null
+++ b/src/main/java/com/qyft/ms/controller/CMDController.java
@@ -0,0 +1,47 @@
+package com.qyft.ms.controller;
+
+import cn.hutool.json.JSONUtil;
+import com.qyft.ms.model.form.CMDForm;
+import com.qyft.ms.service.CMDService;
+import com.qyft.ms.system.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.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.*;
+
+@Tag(name = "控制指令")
+@RestController
+@RequestMapping("/api/cmd")
+@RequiredArgsConstructor
+@Slf4j
+public class CMDController {
+
+ private final CMDService cmdService;
+
+ @Operation(summary = "执行指令")
+ @PostMapping("/execute")
+ public Result execute(@RequestBody CMDForm cmdForm) {
+ try {
+ String commandId = UUID.randomUUID().toString();
+ if (cmdForm.getCommandId() == null || cmdForm.getCommandId().isEmpty()) {
+ cmdForm.setCommandId(commandId);
+ }
+ log.info("接收到指令: {}", JSONUtil.toJsonStr(cmdForm));
+ if (cmdService.executeCommand(cmdForm)) {
+ return Result.success(cmdForm.getCommandId());
+ } else {
+ return Result.failed("无效命令");
+ }
+ } catch (Exception e) {
+ log.error("指令执行异常: {}", JSONUtil.toJsonStr(cmdForm), e);
+ return Result.failed("执行失败");
+ }
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/controller/LogsController.java b/src/main/java/com/qyft/ms/controller/LogsController.java
new file mode 100644
index 0000000..b5e461a
--- /dev/null
+++ b/src/main/java/com/qyft/ms/controller/LogsController.java
@@ -0,0 +1,55 @@
+package com.qyft.ms.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qyft.ms.model.entity.Logs;
+import com.qyft.ms.service.ILogsService;
+import com.qyft.ms.system.common.base.BasePageQuery;
+import com.qyft.ms.system.common.result.PageResult;
+import com.qyft.ms.system.common.result.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Tag(name = "日志")
+@RestController
+@RequestMapping("/api/logs")
+@RequiredArgsConstructor
+@Slf4j
+public class LogsController {
+ private final ILogsService logsService;
+
+ @Operation(summary = "日志列表")
+ @GetMapping("/list")
+ public PageResult getAllTasks(BasePageQuery pageQuery) {
+
+ return PageResult.success(logsService.page(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize())));
+ }
+
+ @Operation(summary = "日志新增")
+ @PostMapping("/add")
+ public Result add(String log) {
+ return Result.success(logsService.insertLog(log));
+ }
+
+ @Operation(summary = "日志详情")
+ @GetMapping("/")
+ public Result detail(@RequestParam Long id) {
+ return Result.success(logsService.getBaseMapper().selectById(id));
+ }
+
+ @Operation(summary = "删除日志")
+ @DeleteMapping("/{ids}")
+ public Result deleteLog(@Parameter(description = "日志ID,多个以英文逗号(,)分割") @PathVariable String ids) {
+ List idsArr = Arrays.stream(ids.split(","))
+ .map(Long::parseLong)
+ .collect(Collectors.toList());
+ return Result.success(logsService.removeByIds(idsArr));
+ }
+}
diff --git a/src/main/java/com/qyft/ms/controller/SysSettingsController.java b/src/main/java/com/qyft/ms/controller/SysSettingsController.java
new file mode 100644
index 0000000..309dea5
--- /dev/null
+++ b/src/main/java/com/qyft/ms/controller/SysSettingsController.java
@@ -0,0 +1,56 @@
+package com.qyft.ms.controller;
+
+import com.qyft.ms.model.dto.SysSettingsDTO;
+import com.qyft.ms.service.ISysSettingsService;
+import com.qyft.ms.system.common.result.Result;
+import io.micrometer.common.lang.Nullable;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "系统配置")
+@RestController
+@RequestMapping("/api/sys")
+@RequiredArgsConstructor
+@Slf4j
+public class SysSettingsController {
+ private final ISysSettingsService sysSettingsService;
+
+ @Operation(summary = "获取配置")
+ @GetMapping("/getConfig")
+ public Result> getConfig(
+ @Parameter(
+ description = "配置类型(heat_area 工作区配置 solution_area 加液区配置 lid_area 拍子区域配置 sys_setting 其他系统配置)"
+ )
+ @RequestParam
+ @Nullable
+ String type
+ ) {
+ if (type == null) {
+ return Result.success(sysSettingsService.getAllConfig());
+ } else {
+ return Result.success(sysSettingsService.getConfig(type));
+ }
+ }
+
+ @Operation(summary = "更新配置")
+ @PutMapping("/updateConfig")
+ public Result updateConfig(@RequestBody List dto) {
+ return Result.failed();
+ }
+
+ @Operation(summary = "修改系统日期与时间(Linux date -s)")
+ @PutMapping("/setSysDate/{newTime}")
+ public Result setSysDate(@Parameter(description = "时间字符串 YYYY-MM-DD HH:mm:ss") @PathVariable String newTime) {
+ boolean isSuccess = sysSettingsService.setSysDate(newTime);
+ if (isSuccess) {
+ return Result.success();
+ }
+ return Result.failed();
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/client/TcpClient.java b/src/main/java/com/qyft/ms/device/client/TcpClient.java
new file mode 100644
index 0000000..f974c4e
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/client/TcpClient.java
@@ -0,0 +1,160 @@
+package com.qyft.ms.device.client;
+
+import cn.hutool.json.JSONUtil;
+import com.qyft.ms.device.common.jsonrpc.JsonRpcRequest;
+import com.qyft.ms.device.config.TcpConfig;
+import com.qyft.ms.device.handler.DeviceMessageHandler;
+import com.qyft.ms.device.model.bo.DeviceFeedback;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.json.JsonObjectDecoder;
+import io.netty.util.CharsetUtil;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TcpClient {
+
+ private final TcpConfig tcpConfig;
+
+ private final DeviceMessageHandler deviceMessageHandler;
+
+ private final EventLoopGroup group = new NioEventLoopGroup();
+ private Channel channel;
+ private Bootstrap bootstrap;
+
+
+ @PostConstruct
+ public void init() {
+ if (tcpConfig.isEnable()) {
+ connect();
+ }
+ }
+
+ public void connect() {
+ try {
+ if (bootstrap == null) {
+ bootstrap = new Bootstrap();
+ bootstrap.group(group)
+ .channel(NioSocketChannel.class)
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, tcpConfig.getTimeout())
+ .handler(new ChannelInitializer<>() {
+ @Override
+ protected void initChannel(Channel ch) {
+ ch.pipeline().addLast(new JsonObjectDecoder());
+ ch.pipeline().addLast(deviceMessageHandler);
+ ch.pipeline().addLast(new TcpConnectionHandler());
+ }
+ });
+ }
+
+ log.info("尝试连接到TCP服务 {}:{}", tcpConfig.getHost(), tcpConfig.getPort());
+ ChannelFuture future = bootstrap.connect(new InetSocketAddress(tcpConfig.getHost(), tcpConfig.getPort()));
+
+ future.addListener((ChannelFutureListener) f -> {
+ if (f.isSuccess()) {
+ channel = f.channel();
+ log.info("已链接到TCP服务");
+ } else {
+ log.error("无法连接到TCP服务. {}ms后重试...", tcpConfig.getReconnect());
+ f.channel().eventLoop().schedule(this::connect, tcpConfig.getReconnect(), TimeUnit.MILLISECONDS);
+ }
+ });
+
+ } catch (Exception e) {
+ log.error("尝试连接到TCP服务发生意外错误: {}", e.getMessage(), e);
+ }
+ }
+
+ @Scheduled(fixedRateString = "${tcp.reconnect}")
+ public void checkConnection() {
+ if (channel == null || !channel.isActive()) {
+ log.error("TCP服务链接丢失");
+ connect();
+ }
+ }
+
+ public boolean send(String request) {
+ if (channel != null && channel.isActive()) {
+ channel.writeAndFlush(Unpooled.copiedBuffer(request, CharsetUtil.UTF_8));
+ return true;
+ } else {
+ log.error("TCP服务未连接,无法发送请求: {}", request);
+ return false;
+ }
+ }
+
+ public DeviceFeedback sendCommand(String method) {
+ JsonRpcRequest request = new JsonRpcRequest();
+ request.setMethod(method);
+ return this.sendCommand(request);
+ }
+
+ public DeviceFeedback sendCommand(String method, Map params) {
+ JsonRpcRequest request = new JsonRpcRequest();
+ request.setMethod(method);
+ request.setParams(params);
+ return this.sendCommand(request);
+ }
+
+ public DeviceFeedback sendCommand(JsonRpcRequest request) {
+ if (request.getId() == null) {
+ request.setId(UUID.randomUUID().toString());
+ }
+ CompletableFuture future = new CompletableFuture<>();
+ deviceMessageHandler.responseMap.put(request.getId(), future);
+ try {
+ if (request.getParams() == null) {
+ request.setParams(new HashMap<>());
+ }
+ request.getParams().put("class", "test");
+ String requestJsonStr = JSONUtil.toJsonStr(request);
+ log.info("发送TCP指令(同步) {}", requestJsonStr);
+ if (this.send(requestJsonStr)) {
+ return future.get(tcpConfig.getFeedbackTimeout(), TimeUnit.MILLISECONDS); // 等待 FEEDBACK 响应
+ } else {
+ return null;
+ }
+ } catch (Exception e) {
+ log.error("发送TCP指令错误(同步) {}", JSONUtil.toJsonStr(request), e);
+ } finally {
+ deviceMessageHandler.responseMap.remove(request.getId()); //确保完成后移除
+ }
+ return null;
+ }
+
+ private class TcpConnectionHandler extends ChannelInboundHandlerAdapter {
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ // 连接断开时的处理逻辑
+ log.error("TCP连接丢失,准备重新连接...");
+ if (channel != null) {
+ channel.close();
+ }
+ connect();
+ super.channelInactive(ctx);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+ log.error("TCP连接发生异常: {}", cause.getMessage(), cause);
+ ctx.close();
+ }
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/common/constant/DeviceCommands.java b/src/main/java/com/qyft/ms/device/common/constant/DeviceCommands.java
new file mode 100644
index 0000000..c1fcc6c
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/common/constant/DeviceCommands.java
@@ -0,0 +1,38 @@
+package com.qyft.ms.device.common.constant;
+
+public class DeviceCommands {
+
+ /**
+ * 使指定轴的电机回原点
+ */
+ public static final String MOTOR_MOVE_TO_HOME = "motorMoveToHome";
+ // 设置指定轴的电机的运行电流
+ public static final String SET_MOTOR_RUNNING_CURRENT = "setMotorRunningCurrent";
+ // 移动指定轴的电机到指定位置
+ public static final String MOVE_MOTOR_TO_POSITION = "moveMotorToPosition";
+ // 设置指定轴的电机的运行速度
+ public static final String SET_MOTOR_SPEED = "setMotorSpeed";
+ // 切换三通阀到基质状态
+ public static final String SWITCH_THREE_WAY_VALVE_TO_SUBSTRATE = "switchThreeWayValveToSubstrate";
+ // 切换三通阀到喷涂状态
+ public static final String SWITCH_THREE_WAY_VALVE_TO_SPRAY = "switchThreeWayValveToSpray";
+ // 控制指定阀的开启或关闭
+ public static final String CONTROL_VALVE = "controlValve";
+ // 以指定亮度开启照明灯板
+ public static final String TURN_ON_LIGHT_PANEL = "turnOnLightPanel";
+ // 关闭照明灯板
+ public static final String TURN_OFF_LIGHT_PANEL = "turnOffLightPanel";
+ // 以指定电压值开启高压电
+ public static final String TURN_ON_HIGH_VOLTAGE = "turnOnHighVoltage";
+ // 关闭高压电
+ public static final String TURN_OFF_HIGH_VOLTAGE = "turnOffHighVoltage";
+ // 以指定转速、方向和时间开启注射泵
+ public static final String TURN_ON_SYRINGE_PUMP = "turnOnSyringePump";
+ // 停止注射泵
+ public static final String TURN_OFF_SYRINGE_PUMP = "turnOffSyringePump";
+ // 设置注射泵的容量、时间比例参数
+ public static final String SET_SYRINGE_PUMP_PARAMETERS = "setSyringePumpParameters";
+ // 推送指定容量的液体
+ public static final String PUSH_VOLUME = "pushVolume";
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/device/common/constant/TcpMessageType.java b/src/main/java/com/qyft/ms/device/common/constant/TcpMessageType.java
new file mode 100644
index 0000000..15f5f8f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/common/constant/TcpMessageType.java
@@ -0,0 +1,16 @@
+package com.qyft.ms.device.common.constant;
+
+public class TcpMessageType {
+ /**
+ * 设备状态
+ */
+ public static final String STATUS = "status";
+ /**
+ * 设备报警
+ */
+ public static final String ALARM = "alarm";
+ /**
+ * 设备指令反馈
+ */
+ public static final String FEEDBACK = "feedback";
+}
diff --git a/src/main/java/com/qyft/ms/device/common/jsonrpc/JsonRpcRequest.java b/src/main/java/com/qyft/ms/device/common/jsonrpc/JsonRpcRequest.java
new file mode 100644
index 0000000..39bdfcb
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/common/jsonrpc/JsonRpcRequest.java
@@ -0,0 +1,24 @@
+package com.qyft.ms.device.common.jsonrpc;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * TCP JSON RPC请求
+ */
+@Data
+public class JsonRpcRequest {
+ /**
+ * 请求id
+ */
+ private String id;
+ /**
+ * 请求方法
+ */
+ private String method;
+ /**
+ * 请求参数
+ */
+ private Map params;
+}
diff --git a/src/main/java/com/qyft/ms/device/common/jsonrpc/JsonRpcResponse.java b/src/main/java/com/qyft/ms/device/common/jsonrpc/JsonRpcResponse.java
new file mode 100644
index 0000000..f97bebc
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/common/jsonrpc/JsonRpcResponse.java
@@ -0,0 +1,16 @@
+package com.qyft.ms.device.common.jsonrpc;
+
+import cn.hutool.json.JSONObject;
+import lombok.Data;
+
+@Data
+public class JsonRpcResponse {
+ /**
+ * 数据类型
+ */
+ private String type;
+ /**
+ * 数据
+ */
+ private JSONObject data;
+}
diff --git a/src/main/java/com/qyft/ms/device/config/TcpConfig.java b/src/main/java/com/qyft/ms/device/config/TcpConfig.java
new file mode 100644
index 0000000..d10cac8
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/config/TcpConfig.java
@@ -0,0 +1,37 @@
+package com.qyft.ms.device.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "tcp")
+public class TcpConfig {
+ /**
+ * 是否启用 TCP 连接
+ */
+ private boolean enable;
+ /**
+ * TCP 链接地址
+ */
+ private String host;
+ /**
+ * TCP 端口
+ */
+ private int port;
+ /**
+ * 断线重连间隔(毫秒)
+ */
+ private int reconnect;
+ /**
+ * 连接超时时间(毫秒)
+ */
+ private int timeout;
+ /**
+ * 指令反馈超时时间
+ */
+ private int feedbackTimeout;
+
+}
+
diff --git a/src/main/java/com/qyft/ms/device/controller/DeviceController.java b/src/main/java/com/qyft/ms/device/controller/DeviceController.java
new file mode 100644
index 0000000..2bd656e
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/controller/DeviceController.java
@@ -0,0 +1,37 @@
+package com.qyft.ms.device.controller;
+
+import com.qyft.ms.device.model.bo.DeviceOperationalStatus;
+import com.qyft.ms.device.model.bo.DeviceStatus;
+import com.qyft.ms.device.service.DeviceStatusService;
+import com.qyft.ms.system.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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "设备控制")
+@RestController
+@RequestMapping("/api/device")
+@RequiredArgsConstructor
+@Slf4j
+public class DeviceController {
+ private final DeviceStatusService deviceStatusService;
+
+ @Operation(summary = "获取设备业务操作状态")
+ @GetMapping("/operational/status")
+ public Result getDeviceOperationalStatus() {
+ DeviceOperationalStatus deviceOperationalStatus = deviceStatusService.getDeviceOperationalStatus();
+ return Result.success(deviceOperationalStatus);
+ }
+
+
+ @Operation(summary = "获取当前设备状态")
+ @GetMapping("/status")
+ public Result getDeviceStatus() {
+ DeviceStatus deviceStatus = deviceStatusService.getDeviceStatus();
+ return Result.success(deviceStatus);
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/controller/DeviceCtrlController.java b/src/main/java/com/qyft/ms/device/controller/DeviceCtrlController.java
new file mode 100644
index 0000000..fee4e55
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/controller/DeviceCtrlController.java
@@ -0,0 +1,93 @@
+package com.qyft.ms.device.controller;
+
+import com.qyft.ms.device.model.bo.DeviceCtrlFuncCMD;
+import com.qyft.ms.device.model.entity.CtrlFunc;
+import com.qyft.ms.device.model.form.CtrlFuncForm;
+import com.qyft.ms.device.model.vo.DeviceCtrlFuncVO;
+import com.qyft.ms.device.service.ICtrlFuncService;
+import com.qyft.ms.system.common.result.Result;
+import com.qyft.ms.system.common.result.ResultCode;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Tag(name = "设备控制")
+@RestController
+@RequestMapping("/api/device")
+@RequiredArgsConstructor
+@Slf4j
+public class DeviceCtrlController {
+ private final ICtrlFuncService ctrlFuncService;
+
+ @Operation(summary = "获取所有设备控制方法步骤")
+ @GetMapping("/ctrl/step")
+ public Result> getAllCtrlFuncStep() {
+ List ctrlFuncList = ctrlFuncService.findAllStepCMD();
+ return Result.success(ctrlFuncList);
+ }
+
+ @Operation(summary = "获取所有设备控制方法")
+ @GetMapping("/ctrl")
+ public Result> getAllCtrlFunc() {
+ List ctrlFuncList = ctrlFuncService.findAll();
+ return Result.success(ctrlFuncList);
+ }
+
+ @Operation(summary = "根据id获取设备控制方法与步骤")
+ @GetMapping("/ctrl/{id}")
+ public Result getById(@PathVariable Long id) {
+ DeviceCtrlFuncVO ctrlFuncVO = ctrlFuncService.findVOById(id);
+ return Result.success(ctrlFuncVO);
+ }
+
+ @Operation(summary = "添加设备控制方法与步骤")
+ @PostMapping("/ctrl")
+ public Result addCtrlFunc(@RequestBody CtrlFuncForm ctrlFuncForm) {
+ long count = ctrlFuncService.countFuncCmdByFuncCmd(ctrlFuncForm.getFuncCmd());
+ if (count > 0) {
+ return Result.failed(ResultCode.DATA_ALREADY_EXISTS);
+
+ }
+ boolean isSuccess = ctrlFuncService.addCtrlFunc(ctrlFuncForm);
+ if (isSuccess) {
+ return Result.success();
+ }
+ return Result.failed();
+ }
+
+ @Operation(summary = "更新设备控制方法与步骤")
+ @PutMapping("/ctrl/{id}")
+ public Result updateCtrlFunc(@PathVariable Long id, @RequestBody CtrlFuncForm ctrlFuncForm) {
+ CtrlFunc currentCtrlFunc = ctrlFuncService.getById(id);
+ if (currentCtrlFunc != null && !currentCtrlFunc.getFuncCmd().equals(ctrlFuncForm.getFuncCmd())) {
+ //改了指令需要判断指令是否重复
+ long count = ctrlFuncService.countFuncCmdByFuncCmd(ctrlFuncForm.getFuncCmd());
+ if (count > 0) {
+ return Result.failed(ResultCode.DATA_ALREADY_EXISTS);
+ }
+ }
+ boolean isSuccess = ctrlFuncService.updateCtrlFunc(id, ctrlFuncForm);
+ if (isSuccess) {
+ return Result.success();
+ }
+
+ return Result.failed();
+ }
+
+ @Operation(summary = "删除设备控制方法")
+ @DeleteMapping("/ctrl/{ids}")
+ public Result deleteCtrlFunc(@Parameter(description = "设备控制方法ID,多个以英文逗号(,)分割") @PathVariable String ids) {
+ boolean isSuccess = ctrlFuncService.deleteCtrlFunc(ids);
+ if (isSuccess) {
+ return Result.success();
+ }
+ return Result.failed();
+ }
+
+
+}
diff --git a/src/main/java/com/qyft/ms/device/controller/TestController.java b/src/main/java/com/qyft/ms/device/controller/TestController.java
new file mode 100644
index 0000000..495fc28
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/controller/TestController.java
@@ -0,0 +1,43 @@
+package com.qyft.ms.device.controller;
+
+import com.qyft.ms.device.model.bo.DeviceStatus;
+import com.qyft.ms.device.service.DeviceTcpCMDService;
+import com.qyft.ms.device.service.DeviceStatusService;
+import com.qyft.ms.system.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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "测试")
+@RestController
+@RequestMapping("/api/test")
+@RequiredArgsConstructor
+@Slf4j
+public class TestController {
+ private final DeviceTcpCMDService deviceTcpCMDService;
+ private final DeviceStatusService deviceStatusService;
+
+ @Operation(summary = "生成设备状态实体")
+ @GetMapping("/getDeviceStatus")
+ public Result getDeviceStatus() {
+ DeviceStatus deviceStatus = deviceStatusService.getDeviceStatus();
+ return Result.success(deviceStatus);
+ }
+
+ @Operation(summary = "触发测试报警")
+ @GetMapping("/alarmTest")
+ public Result alarmTest(@RequestParam String code, @RequestParam String msg, @RequestParam String module) {
+ boolean success = deviceTcpCMDService.alarmTest(code, msg, module);
+ if (success) {
+ return Result.success();
+ } else {
+ return Result.failed("触发测试报警失败");
+ }
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/device/handler/DeviceMessageHandler.java b/src/main/java/com/qyft/ms/device/handler/DeviceMessageHandler.java
new file mode 100644
index 0000000..aa00420
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/handler/DeviceMessageHandler.java
@@ -0,0 +1,68 @@
+package com.qyft.ms.device.handler;
+
+import cn.hutool.json.JSONUtil;
+import com.qyft.ms.common.constant.WebSocketMessageType;
+import com.qyft.ms.device.common.constant.TcpMessageType;
+import com.qyft.ms.device.common.jsonrpc.JsonRpcResponse;
+import com.qyft.ms.device.model.bo.DeviceAlarm;
+import com.qyft.ms.device.model.bo.DeviceFeedback;
+import com.qyft.ms.device.model.bo.DeviceStatus;
+import com.qyft.ms.device.service.DeviceStatusService;
+import com.qyft.ms.service.WebSocketService;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.util.CharsetUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+@ChannelHandler.Sharable
+@RequiredArgsConstructor
+public class DeviceMessageHandler extends ChannelInboundHandlerAdapter {
+
+ public final Map> responseMap = new ConcurrentHashMap<>();
+ private final DeviceStatusService deviceStatusService;
+ private final WebSocketService webSocketService;
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) {
+ ByteBuf buf = (ByteBuf) msg;
+ String serverMsg = buf.toString(CharsetUtil.UTF_8);
+ try {
+ JsonRpcResponse jsonRpcResponse = JSONUtil.toBean(serverMsg, JsonRpcResponse.class);
+ if (TcpMessageType.STATUS.equals(jsonRpcResponse.getType())) {//设备状态
+ DeviceStatus deviceStatus = JSONUtil.toBean(jsonRpcResponse.getData(), DeviceStatus.class);
+ deviceStatusService.updateDeviceStatus(deviceStatus); // 更新设备状态
+ } else if (TcpMessageType.ALARM.equals(jsonRpcResponse.getType())) {//设备报警
+ log.error("设备报警: {}", serverMsg);
+ DeviceAlarm deviceAlarm = JSONUtil.toBean(jsonRpcResponse.getData(), DeviceAlarm.class);
+ webSocketService.pushMsg(WebSocketMessageType.ALARM, deviceAlarm);
+ } else if (TcpMessageType.FEEDBACK.equals(jsonRpcResponse.getType())) {//设备指令反馈
+ DeviceFeedback deviceFeedback = JSONUtil.toBean(jsonRpcResponse.getData(), DeviceFeedback.class);
+ this.handleTcpResponse(deviceFeedback);
+ }
+ } catch (Exception e) {
+ log.error("TCP服务消息处理错误: {}, error: {}", serverMsg, e.getMessage(), e);
+ } finally {
+ buf.release();
+ }
+ }
+
+ private void handleTcpResponse(DeviceFeedback deviceFeedback) {
+ String requestId = deviceFeedback.getId();
+ CompletableFuture future = responseMap.remove(requestId);
+ if (future != null) {
+ future.complete(deviceFeedback);
+ } else {
+ log.error("未找到 requestId: {} 对应的等待请求", requestId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/device/mapper/CtrlFuncMapper.java b/src/main/java/com/qyft/ms/device/mapper/CtrlFuncMapper.java
new file mode 100644
index 0000000..1f48c6d
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/mapper/CtrlFuncMapper.java
@@ -0,0 +1,11 @@
+package com.qyft.ms.device.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qyft.ms.device.model.entity.CtrlFunc;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CtrlFuncMapper extends BaseMapper {
+
+}
+
diff --git a/src/main/java/com/qyft/ms/device/mapper/CtrlFuncStepMapper.java b/src/main/java/com/qyft/ms/device/mapper/CtrlFuncStepMapper.java
new file mode 100644
index 0000000..4077a69
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/mapper/CtrlFuncStepMapper.java
@@ -0,0 +1,11 @@
+package com.qyft.ms.device.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qyft.ms.device.model.entity.CtrlFuncStep;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CtrlFuncStepMapper extends BaseMapper {
+
+}
+
diff --git a/src/main/java/com/qyft/ms/device/model/bo/DeviceAlarm.java b/src/main/java/com/qyft/ms/device/model/bo/DeviceAlarm.java
new file mode 100644
index 0000000..f6b19f6
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/bo/DeviceAlarm.java
@@ -0,0 +1,18 @@
+package com.qyft.ms.device.model.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 设备报警信息
+ */
+@Schema(description = "设备报警信息")
+@Data
+public class DeviceAlarm {
+ @Schema(description = "报警代码")
+ private String code;
+ @Schema(description = "报警信息")
+ private String msg;
+ @Schema(description = "报警模块")
+ private String module;
+}
diff --git a/src/main/java/com/qyft/ms/device/model/bo/DeviceCtrlFuncCMD.java b/src/main/java/com/qyft/ms/device/model/bo/DeviceCtrlFuncCMD.java
new file mode 100644
index 0000000..fb5bf09
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/bo/DeviceCtrlFuncCMD.java
@@ -0,0 +1,27 @@
+package com.qyft.ms.device.model.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "设备控制方法步骤指令")
+@Data
+public class DeviceCtrlFuncCMD {
+
+ public DeviceCtrlFuncCMD(String name, String deviceCmd, String params) {
+ this.name = name;
+ this.deviceCmd = deviceCmd;
+ this.params = params;
+ }
+
+ @Schema(description = "指令名称")
+ private String name;
+
+ @Schema(description = "设备指令")
+ private String deviceCmd;
+
+ @Schema(description = "参数")
+ private String params;
+
+}
diff --git a/src/main/java/com/qyft/ms/device/model/bo/DeviceFeedback.java b/src/main/java/com/qyft/ms/device/model/bo/DeviceFeedback.java
new file mode 100644
index 0000000..8b1490d
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/bo/DeviceFeedback.java
@@ -0,0 +1,26 @@
+package com.qyft.ms.device.model.bo;
+
+import lombok.Data;
+
+/**
+ * 设备当前状态
+ */
+@Data
+public class DeviceFeedback {
+ /**
+ * 请求id
+ */
+ private String id;
+ /**
+ * 请求数据
+ */
+ private Object result;
+
+ private DeviceFeedbackError error;
+
+ @Data
+ static class DeviceFeedbackError {
+ private String code;
+ private String message;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/model/bo/DeviceOperationalStatus.java b/src/main/java/com/qyft/ms/device/model/bo/DeviceOperationalStatus.java
new file mode 100644
index 0000000..cf5643d
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/bo/DeviceOperationalStatus.java
@@ -0,0 +1,38 @@
+package com.qyft.ms.device.model.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Schema(description = "设备业务操作状态")
+@Data
+public class DeviceOperationalStatus {
+ @Schema(description = "托盘列表")
+ private List trayList = new ArrayList<>();
+
+ /**
+ * 托盘
+ */
+ @Data
+ static class Tray {
+ @Schema(description = "所在加热区id")
+ private Long heatId;
+ @Schema(description = "是否在加液区")
+ private boolean isSolutionArea;
+ @Schema(description = "试管列表")
+ private List tubeList;
+ }
+
+ /**
+ * 试管
+ */
+ @Data
+ static class Tube {
+ @Schema(description = "试管编号")
+ private Integer tubeNum;
+ @Schema(description = "试管内是否有样品")
+ private boolean isSample;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/model/bo/DeviceStatus.java b/src/main/java/com/qyft/ms/device/model/bo/DeviceStatus.java
new file mode 100644
index 0000000..e68fd7a
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/bo/DeviceStatus.java
@@ -0,0 +1,140 @@
+package com.qyft.ms.device.model.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 设备当前状态
+ */
+@Schema(description = "设备当前传感器状态")
+@Data
+public class DeviceStatus {
+
+ @Schema(description = "是否是急停状态,true为急停")
+ private Boolean emergencyStop;
+
+ @Schema(description = "门状态,true为开启状态,false为关闭状态")
+ private Boolean doorStatus;
+
+ @Schema(description = "导轨机械臂状态")
+ private RailArm railArm;
+
+ @Schema(description = "加液操作区属性")
+ private LiquidArea liquidArea;
+
+ @Schema(description = "加热操作区属性")
+ private List heatArea;
+
+ @Schema(description = "碱容器状态")
+ private LiquidBucket alkaliBucket;
+
+
+ /**
+ * 导轨机械臂属性
+ */
+ @Data
+ public static class RailArm {
+ @Schema(description = "当前所在x坐标")
+ private Integer x;
+ @Schema(description = "当前所在y坐标")
+ private Integer y;
+ @Schema(description = "当前所在z坐标")
+ private Integer z;
+ @Schema(description = "当前轴1角度")
+ private Double joint1;
+ @Schema(description = "当前轴2角度")
+ private Double joint2;
+ @Schema(description = "当前机械臂(轴3)上下移动距离")
+ private Integer distance;
+ @Schema(description = "当前导轨移动距离")
+ private Double railDistance;
+ @Schema(description = "当前夹爪移动距离")
+ private Double clawDistance;
+ @Schema(description = "夹爪状态,true为张开状态,false为闭合状态")
+ private Boolean clawStatus;
+ @Schema(description = "导轨是否在原点,true为在原点")
+ private Boolean isZeroPos;
+ @Schema(description = "导轨是否在限位点,true为在限位点")
+ private Boolean isLimitPos;
+ }
+
+
+ /**
+ * 加液操作区属性
+ */
+ @Data
+ public static class LiquidArea {
+ @Schema(description = "加液机械臂状态")
+ private LiquidArm liquidArm;
+
+ @Schema(description = "是否正在摇匀,true为正在摇匀,false为停止摇匀")
+ private Boolean isShaking;
+
+ @Schema(description = "是否存在托盘,true为存在托盘,false为无托盘")
+ private Boolean liquidTray;
+
+ @Schema(description = "溶液容器状态")
+ private List solutionBucket;
+ }
+
+ /**
+ * 加热操作区属性
+ */
+ @Data
+ public static class HeatArea {
+ @Schema(description = "加热器设备id")
+ private String hardwareId;
+ @Schema(description = "托盘状态,0为无托盘,1为有托盘,2为托盘抬起")
+ private Integer trayStatus;
+ @Schema(description = "是否正在加热,true为正在加热,false为未加热")
+ private Boolean isHeating;
+ @Schema(description = "拍子状态,true为存在拍子,false无拍子")
+ private Boolean capStatus;
+ @Schema(description = "拍子密封状态,true为已密封,false为未密封")
+ private Boolean isSealed;
+ @Schema(description = "加热器当前温度")
+ private Double temperature;
+ }
+
+
+ /**
+ * 加液机械臂属性
+ */
+ @Data
+ public static class LiquidArm {
+ @Schema(description = "当前所在x坐标")
+ private Integer x;
+ @Schema(description = "当前所在y坐标")
+ private Integer y;
+ @Schema(description = "当前所在z坐标")
+ private Integer z;
+ @Schema(description = "当前轴1角度")
+ private Double joint1;
+ @Schema(description = "当前轴2角度")
+ private Double joint2;
+ @Schema(description = "加液泵状态")
+ private List pump;
+ }
+
+ /**
+ * 泵属性
+ */
+ @Data
+ public static class Pump {
+ @Schema(description = "加液泵id")
+ private String pumpId;
+ @Schema(description = "是否正在加液,true正在加液,false未运行")
+ private Integer isPumping;
+ }
+
+
+ @Data
+ public static class LiquidBucket {
+ @Schema(description = "容器是否为空,true为空,false不为空")
+ private Boolean isEmpty;
+ @Schema(description = "容器是否为满,true为满,false不满")
+ private Boolean isFull;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/model/entity/CtrlFunc.java b/src/main/java/com/qyft/ms/device/model/entity/CtrlFunc.java
new file mode 100644
index 0000000..fcc09f8
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/entity/CtrlFunc.java
@@ -0,0 +1,23 @@
+package com.qyft.ms.device.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "设备控制方法记录表")
+@TableName("ctrl_func")
+@Data
+public class CtrlFunc extends BaseEntity {
+ @NotBlank
+ @Schema(description = "控制方法的名称")
+ private String name;
+
+ @NotBlank
+ @Schema(description = "控制方法的指令")
+ private String funcCmd;
+
+}
diff --git a/src/main/java/com/qyft/ms/device/model/entity/CtrlFuncStep.java b/src/main/java/com/qyft/ms/device/model/entity/CtrlFuncStep.java
new file mode 100644
index 0000000..e6b1f32
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/entity/CtrlFuncStep.java
@@ -0,0 +1,27 @@
+package com.qyft.ms.device.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "设备控制方法步骤表")
+@TableName("ctrl_func_step")
+@Data
+public class CtrlFuncStep extends BaseEntity {
+
+ @Schema(description = "控制方法的指令")
+ private String funcCmd;
+
+ @Schema(description = "设备指令")
+ private String deviceCmd;
+
+ @Schema(description = "备注说明")
+ private String remarks;
+
+ @Schema(description = "设备指令的参数,为null则动态传入")
+ private String params;
+
+}
diff --git a/src/main/java/com/qyft/ms/device/model/form/CtrlFuncForm.java b/src/main/java/com/qyft/ms/device/model/form/CtrlFuncForm.java
new file mode 100644
index 0000000..713a60c
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/form/CtrlFuncForm.java
@@ -0,0 +1,38 @@
+package com.qyft.ms.device.model.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+import java.util.Map;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "设备控制方法表单")
+@Data
+public class CtrlFuncForm {
+
+ @Schema(description = "控制方法的名称")
+ private String name;
+
+ @Schema(description = "控制方法的指令")
+ private String funcCmd;
+
+ @Schema(description = "设备控制方法步骤")
+ private List ctrlFuncStepList;
+
+ @Data
+ public static class CtrlFuncStepForm {
+
+ @Schema(description = "设备指令")
+ private String deviceCmd;
+
+ @Schema(description = "备注说明")
+ private String remarks;
+
+ @Schema(description = "设备指令的参数,为null则动态传入")
+ private Map params;
+
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/device/model/vo/CtrlFuncVO.java b/src/main/java/com/qyft/ms/device/model/vo/CtrlFuncVO.java
new file mode 100644
index 0000000..6655749
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/vo/CtrlFuncVO.java
@@ -0,0 +1,26 @@
+package com.qyft.ms.device.model.vo;
+
+import com.qyft.ms.device.model.entity.CtrlFuncStep;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "获取所有设备控制方法返回数据")
+@Data
+public class CtrlFuncVO {
+
+ private Long id;
+
+ @Schema(description = "控制方法的名称")
+ private String name;
+
+ @Schema(description = "控制方法的指令")
+ private String funcCmd;
+
+ @Schema(description = "设备控制方法步骤列表")
+ private List ctrlFuncStepList;
+
+}
diff --git a/src/main/java/com/qyft/ms/device/model/vo/DeviceCtrlFuncVO.java b/src/main/java/com/qyft/ms/device/model/vo/DeviceCtrlFuncVO.java
new file mode 100644
index 0000000..7d16cda
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/model/vo/DeviceCtrlFuncVO.java
@@ -0,0 +1,26 @@
+package com.qyft.ms.device.model.vo;
+
+import com.qyft.ms.device.model.entity.CtrlFuncStep;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "获取所有设备控制方法返回数据")
+@Data
+public class DeviceCtrlFuncVO {
+
+ private Long id;
+
+ @Schema(description = "控制方法的名称")
+ private String name;
+
+ @Schema(description = "控制方法的指令")
+ private String funcCmd;
+
+ @Schema(description = "设备控制方法步骤列表")
+ private List ctrlFuncStepList;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/device/service/DeviceAlarmService.java b/src/main/java/com/qyft/ms/device/service/DeviceAlarmService.java
new file mode 100644
index 0000000..73b0cb8
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/DeviceAlarmService.java
@@ -0,0 +1,12 @@
+package com.qyft.ms.device.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 设备报警服务
+ */
+@Service
+@RequiredArgsConstructor
+public class DeviceAlarmService {
+}
diff --git a/src/main/java/com/qyft/ms/device/service/DeviceService.java b/src/main/java/com/qyft/ms/device/service/DeviceService.java
new file mode 100644
index 0000000..c84d6a9
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/DeviceService.java
@@ -0,0 +1,14 @@
+package com.qyft.ms.device.service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 设备操作
+ */
+@Service
+@RequiredArgsConstructor
+public class DeviceService {
+
+
+}
diff --git a/src/main/java/com/qyft/ms/device/service/DeviceStatusService.java b/src/main/java/com/qyft/ms/device/service/DeviceStatusService.java
new file mode 100644
index 0000000..b7fd429
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/DeviceStatusService.java
@@ -0,0 +1,47 @@
+package com.qyft.ms.device.service;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.qyft.ms.device.model.bo.DeviceOperationalStatus;
+import com.qyft.ms.device.model.bo.DeviceStatus;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 设备传感器状态推送
+ */
+@Service
+@RequiredArgsConstructor
+public class DeviceStatusService {
+ /**
+ * 设备运行状态
+ */
+ private final DeviceStatus deviceStatus = new DeviceStatus();
+ /**
+ * 设备业务状态
+ * -- GETTER --
+ * 更新设备业务操作状态
+ */
+ @Getter
+ private final DeviceOperationalStatus deviceOperationalStatus = new DeviceOperationalStatus();
+
+ /**
+ * 更新设备状态
+ */
+ public void updateDeviceStatus(DeviceStatus newStatus) {
+ synchronized (deviceStatus) {
+ BeanUtil.copyProperties(newStatus, deviceStatus);
+ }
+ }
+
+ /**
+ * 获取设备状态
+ */
+ public DeviceStatus getDeviceStatus() {
+ synchronized (deviceStatus) {
+ return BeanUtil.copyProperties(deviceStatus, DeviceStatus.class);
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/qyft/ms/device/service/DeviceStepService.java b/src/main/java/com/qyft/ms/device/service/DeviceStepService.java
new file mode 100644
index 0000000..ef0f78e
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/DeviceStepService.java
@@ -0,0 +1,166 @@
+package com.qyft.ms.device.service;
+
+import com.qyft.ms.model.bo.TubeSol;
+import com.qyft.ms.service.CMDService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 设备步骤操作
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DeviceStepService {
+
+ CMDService cmdService;
+
+ /**
+ * 抬起托盘
+ *
+ * @param heatId 加热区id
+ */
+ public boolean upTray(String heatId) {
+// Map params = Map.of("heatId", heatId);
+// List> cmdList = cmdService.upTray(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ /**
+ * 降下托盘
+ *
+ * @param heatId 加热区id
+ */
+ public boolean downTray(String heatId) {
+// Map params = Map.of("heatId", heatId);
+// List> cmdList = cmdService.downTray(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ /**
+ * 添加溶液
+ *
+ * @param tubeSolList 需要添加溶液的试管与溶液
+ */
+ public boolean addLiquid(List tubeSolList) {
+// for (TubeSol tubeSol : tubeSolList) {
+// Map params = Map.of("tubeNum", tubeSol.getTubeNum(), "solutionId", tubeSol.getSolId(), "volume", tubeSol.getVolume());
+// List> cmdList = cmdService.downTray(params);
+// boolean result = cmdList.stream().allMatch(Supplier::get);
+// if (!result) {
+// return false;
+// }
+// }
+ return true;
+ }
+
+ /**
+ * 将指定加热区的托盘移至加液区
+ *
+ * @param heatId 加热区id
+ */
+ public boolean moveToSol(String heatId) {
+// Map params = Map.of("heatId", heatId);
+// List> cmdList = cmdService.moveToActionArea(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ /**
+ * 移至加热
+ *
+ * @param heatId 加热区id
+ */
+ public boolean moveToHeat(String heatId) {
+// Map params = Map.of("heatId", heatId);
+// List> cmdList = cmdService.moveToHeatArea(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ /**
+ * 摇匀
+ *
+ * @param second 摇匀时间
+ */
+ public boolean shaking(int second) {
+// Map params = Map.of();
+// List> cmdList = cmdService.startShakeUp(params);
+// boolean result = cmdList.stream().allMatch(Supplier::get);
+// if (result) {
+// this.delay(second);
+// cmdList = cmdService.stopShakeUp(params);
+// result = cmdList.stream().allMatch(Supplier::get);
+// return result;
+// }
+// return false;
+ return true;
+ }
+
+ /**
+ * 开始加热
+ *
+ * @param heatId 加热区id
+ * @param temperature 目标温度
+ */
+ public boolean startHeating(String heatId, double temperature) {
+// Map params = Map.of("heatId", heatId, "temperature", temperature);
+// List> cmdList = cmdService.startHeat(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ /**
+ * 停止加热
+ *
+ * @param heatId 加热区id
+ */
+ public boolean stopHeating(String heatId) {
+// Map params = Map.of("heatId", heatId);
+// List> cmdList = cmdService.stopHeat(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ /**
+ * 停止加热
+ */
+ public boolean takePhoto() {
+// Map params = Map.of();
+// List> cmdList = cmdService.takePhoto(params);
+// return cmdList.stream().allMatch(Supplier::get);
+ return true;
+ }
+
+ //移至异常
+ public boolean moveToExc() {
+ return true;
+ }
+
+ //移除异常
+ public boolean moveOutToExc() {
+ return true;
+ }
+
+ /**
+ * 等待
+ *
+ * @param second 秒
+ */
+ public boolean delay(int second) {
+ try {
+ Thread.sleep(second * 1000L);
+ return true;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return false;
+ }
+
+
+}
diff --git a/src/main/java/com/qyft/ms/device/service/DeviceTcpCMDService.java b/src/main/java/com/qyft/ms/device/service/DeviceTcpCMDService.java
new file mode 100644
index 0000000..eebe90d
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/DeviceTcpCMDService.java
@@ -0,0 +1,261 @@
+package com.qyft.ms.device.service;
+
+import cn.hutool.json.JSONUtil;
+import com.qyft.ms.device.client.TcpClient;
+import com.qyft.ms.device.common.constant.DeviceCommands;
+import com.qyft.ms.device.common.jsonrpc.JsonRpcRequest;
+import com.qyft.ms.device.model.bo.DeviceFeedback;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * 设备tcp指令发送服务
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DeviceTcpCMDService {
+
+ private final TcpClient tcpClient;
+
+ private final BlockingDeque> taskQueue = new LinkedBlockingDeque<>();
+
+ private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+
+ @PostConstruct
+ private void initExecutorThread() {
+ new Thread(this::executeTasks).start();
+ }
+
+ private boolean putTask(String method) {
+ JsonRpcRequest request = new JsonRpcRequest();
+ request.setMethod(method);
+ return this.putTask(request);
+ }
+
+ private boolean putTask(String method, Map params) {
+ JsonRpcRequest request = new JsonRpcRequest();
+ request.setMethod(method);
+ request.setParams(params);
+ return this.putTask(request);
+ }
+
+ private boolean putTask(JsonRpcRequest request) {
+ Callable task = () -> {
+ DeviceFeedback deviceFeedback = tcpClient.sendCommand(request);
+ if (deviceFeedback == null || deviceFeedback.getError() != null) {
+ log.error("TCP 指令执行错误 request:{} feedback:{}", JSONUtil.toJsonStr(request), JSONUtil.toJsonStr(deviceFeedback));
+ return false;
+ }
+ return true;
+ };
+ try {
+ taskQueue.put(task);
+ return executorService.submit(taskQueue.take()).get();
+ } catch (Exception e) {
+ log.error("TCP 指令执行错误 request:{}", JSONUtil.toJsonStr(request), e);
+ }
+ return false;
+ }
+
+ private void executeTasks() {
+ while (true) {
+ try {
+ Callable task = taskQueue.take();
+ task.call();
+ } catch (Exception e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+
+ /**
+ * 使指定轴的电机回原点
+ * @param axis 轴标识,可选值为 "X", "Y", "Z"
+ * @return 操作是否成功
+ */
+
+ public boolean motorMoveToHome(String axis) {
+ Map params = new HashMap<>();
+ params.put("axis", axis);
+ return this.putTask(DeviceCommands.MOTOR_MOVE_TO_HOME, params);
+ }
+
+
+ /**
+ * 设置指定轴的电机运行时电流
+ * @param axis 轴标识,可选值为 "X", "Y", "Z"
+ * @param current 电流值
+ * @return 操作是否成功
+ */
+
+ public boolean setMotorRunningCurrent(String axis, double current) {
+ Map params = new HashMap<>();
+ params.put("axis", axis);
+ params.put("current", current);
+ DeviceFeedback deviceFeedback = tcpClient.sendCommand(DeviceCommands.SET_MOTOR_RUNNING_CURRENT, params);
+ if (deviceFeedback == null || deviceFeedback.getError() != null) {
+ log.error("TCP setMotorRunningCurrent 指令执行错误 {}", JSONUtil.toJsonStr(deviceFeedback));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 移动指定轴的电机到指定位置
+ * @param axis 轴标识,可选值为 "X", "Y", "Z"
+ * @param position 轴位置,单位:mm
+ * @return 操作是否成功
+ */
+ public boolean moveMotorToPosition(String axis, double position) {
+ Map params = new HashMap<>();
+ params.put("axis", axis);
+ params.put("position", position);
+ return this.putTask(DeviceCommands.MOVE_MOTOR_TO_POSITION, params);
+ }
+
+ /**
+ * 设置指定轴的电机的运行速度
+ * @param axis 轴标识,可选值为 "X", "Y", "Z"
+ * @param speed 速度值
+ * @return 操作是否成功
+ */
+ public boolean setMotorSpeed(String axis, double speed) {
+ Map params = new HashMap<>();
+ params.put("axis", axis);
+ params.put("speed", speed);
+ DeviceFeedback deviceFeedback = tcpClient.sendCommand(DeviceCommands.SET_MOTOR_SPEED, params);
+ if (deviceFeedback == null || deviceFeedback.getError() != null) {
+ log.error("TCP setMotorSpeed 指令执行错误 {}", JSONUtil.toJsonStr(deviceFeedback));
+ return false;
+ }
+ return true;
+ }
+
+ // 三通阀控制方法
+ /**
+ * 切换三通阀到基质状态
+ * @return 操作是否成功
+ */
+ public boolean switchThreeWayValveToSubstrate() {
+ return this.putTask(DeviceCommands.SWITCH_THREE_WAY_VALVE_TO_SUBSTRATE);
+ }
+
+ /**
+ * 切换三通阀到喷涂状态
+ * @return 操作是否成功
+ */
+ public boolean switchThreeWayValveToSpray() {
+ return this.putTask(DeviceCommands.SWITCH_THREE_WAY_VALVE_TO_SPRAY);
+ }
+
+ // 除湿阀、清洗阀、喷嘴阀控制方法
+ /**
+ * 控制指定阀的开启或关闭
+ * @param valveType 阀类型,可选值为 "Dehumidification", "Cleaning", "Nozzle"
+ * @param isOpen 是否开启,true 为开启,false 为关闭
+ * @return 操作是否成功
+ */
+ public boolean controlValve(String valveType, boolean isOpen) {
+ Map params = new HashMap<>();
+ params.put("valveType", valveType);
+ params.put("isOpen", isOpen);
+ return this.putTask(DeviceCommands.CONTROL_VALVE, params);
+ }
+
+ // 照明灯板控制方法
+ /**
+ * 以指定亮度开启照明灯板
+ * @param brightness 亮度值,范围 0 - 100
+ * @return 操作是否成功
+ */
+ public boolean turnOnLightPanel(int brightness) {
+ Map params = new HashMap<>();
+ params.put("brightness", brightness);
+ return this.putTask(DeviceCommands.TURN_ON_LIGHT_PANEL, params);
+ }
+
+ /**
+ * 关闭照明灯板
+ * @return 操作是否成功
+ */
+ public boolean turnOffLightPanel() {
+ return this.putTask(DeviceCommands.TURN_OFF_LIGHT_PANEL);
+ }
+
+ // 高压电控制方法
+ /**
+ * 以指定电压值开启高压电
+ * @param voltage 电压值
+ * @return 操作是否成功
+ */
+ public boolean turnOnHighVoltage(double voltage) {
+ Map params = new HashMap<>();
+ params.put("voltage", voltage);
+ return this.putTask(DeviceCommands.TURN_ON_HIGH_VOLTAGE, params);
+ }
+
+ /**
+ * 关闭高压电
+ * @return 操作是否成功
+ */
+ public boolean turnOffHighVoltage() {
+ return this.putTask(DeviceCommands.TURN_OFF_HIGH_VOLTAGE);
+ }
+
+ // 注射泵控制方法
+ /**
+ * 以指定转速、方向和时间开启注射泵
+ * @param rotationSpeed 转速
+ * @param direction 方向
+ * @param time 时间
+ * @return 操作是否成功
+ */
+ public boolean turnOnSyringePump(double rotationSpeed, String direction, double time) {
+ Map params = new HashMap<>();
+ params.put("rotationSpeed", rotationSpeed);
+ params.put("direction", direction);
+ params.put("time", time);
+ return this.putTask(DeviceCommands.TURN_ON_SYRINGE_PUMP, params);
+ }
+
+ /**
+ * 停止注射泵
+ * @return 操作是否成功
+ */
+ public boolean turnOffSyringePump() {
+ return this.putTask(DeviceCommands.TURN_OFF_SYRINGE_PUMP);
+ }
+
+ /**
+ * 设置注射泵的容量、时间比例参数
+ * @param capacity 容量
+ * @param timeRatio 时间比例
+ * @return 操作是否成功
+ */
+ public boolean setSyringePumpParameters(double capacity, double timeRatio) {
+ Map params = new HashMap<>();
+ params.put("capacity", capacity);
+ params.put("timeRatio", timeRatio);
+ return this.putTask(DeviceCommands.SET_SYRINGE_PUMP_PARAMETERS, params);
+ }
+
+ /**
+ * 推送指定容量的液体
+ * @param volume 容量,单位:ml
+ * @return 操作是否成功
+ */
+ public boolean pushVolume(double volume) {
+ Map params = new HashMap<>();
+ params.put("volume", volume);
+ return this.putTask(DeviceCommands.PUSH_VOLUME, params);
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/service/ICtrlFuncService.java b/src/main/java/com/qyft/ms/device/service/ICtrlFuncService.java
new file mode 100644
index 0000000..904c9b4
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/ICtrlFuncService.java
@@ -0,0 +1,28 @@
+package com.qyft.ms.device.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qyft.ms.device.model.bo.DeviceCtrlFuncCMD;
+import com.qyft.ms.device.model.entity.CtrlFunc;
+import com.qyft.ms.device.model.form.CtrlFuncForm;
+import com.qyft.ms.device.model.vo.DeviceCtrlFuncVO;
+
+import java.util.List;
+
+
+public interface ICtrlFuncService extends IService {
+
+ List findAllStepCMD();
+
+ List findAll();
+
+ DeviceCtrlFuncVO findVOById(Long id);
+
+ long countFuncCmdByFuncCmd(String funcCmd);
+
+ boolean addCtrlFunc(CtrlFuncForm ctrlFuncForm);
+
+ boolean updateCtrlFunc(Long id, CtrlFuncForm ctrlFuncForm);
+
+ boolean deleteCtrlFunc(String idsStr);
+
+}
diff --git a/src/main/java/com/qyft/ms/device/service/ICtrlFuncStepService.java b/src/main/java/com/qyft/ms/device/service/ICtrlFuncStepService.java
new file mode 100644
index 0000000..81ae0d9
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/ICtrlFuncStepService.java
@@ -0,0 +1,12 @@
+package com.qyft.ms.device.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qyft.ms.device.model.entity.CtrlFuncStep;
+
+import java.util.List;
+
+public interface ICtrlFuncStepService extends IService {
+ List selectListByFuncCmd(String funcCmd);
+
+
+}
diff --git a/src/main/java/com/qyft/ms/device/service/impl/CtrlFuncServiceImpl.java b/src/main/java/com/qyft/ms/device/service/impl/CtrlFuncServiceImpl.java
new file mode 100644
index 0000000..139ba4b
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/impl/CtrlFuncServiceImpl.java
@@ -0,0 +1,132 @@
+package com.qyft.ms.device.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qyft.ms.device.mapper.CtrlFuncMapper;
+import com.qyft.ms.device.model.bo.DeviceCtrlFuncCMD;
+import com.qyft.ms.device.model.entity.CtrlFunc;
+import com.qyft.ms.device.model.entity.CtrlFuncStep;
+import com.qyft.ms.device.model.form.CtrlFuncForm;
+import com.qyft.ms.device.model.vo.DeviceCtrlFuncVO;
+import com.qyft.ms.device.service.ICtrlFuncService;
+import com.qyft.ms.device.service.ICtrlFuncStepService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+@Service
+public class CtrlFuncServiceImpl extends ServiceImpl implements ICtrlFuncService {
+
+ private final ICtrlFuncStepService ctrlFuncStepService;
+
+ private static final List ctrlFuncDeviceCMDList = new ArrayList<>();
+
+ static {
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("开门", "openDoor", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("关门", "closeDoor", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("导轨机械臂运动到指定点位", "moveRailArmToPoint", "{\"x\":0,\"y\":0,\"z\":0}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("导轨机械臂相对运动", "moveRailArmRelative", "{\"x\":0,\"y\":0,\"z\":0}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("导轨机械臂移至指定加热区上方", "moveToHeatArea", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("导轨机械臂移至加液区上方", "moveToActionArea", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("导轨机械臂移至拍子存放区上方", "moveToCapArea", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("打开导轨机械臂夹爪", "openClaw", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("收合导轨机械臂夹爪", "closeClaw", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("指定加热区密封拍子", "sealLidOn", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("指定加热区解除密封拍子", "sealLidOff", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("拍子存放区高度向上一个级别", "capHeightUp", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("拍子存放区高度向下移动一个级别", "capHeightDown", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("指定加热区开始加热", "startHeating", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("指定加热区停止加热", "stopHeating", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("指定加热区抬起托盘", "raiseTray", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("指定加热区放下托盘", "lowerTray", "{\"heatId\":2}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("依次添加溶液", "addLiquid", "{\"tubeSolList\":[{\"tubeNum\":1,\"solId\":15,\"volume\":20}]}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("开始摇匀", "startShaking", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("停止摇匀", "stopShaking", "{}"));
+ ctrlFuncDeviceCMDList.add(new DeviceCtrlFuncCMD("拍照", "takePhoto", "{}"));
+ }
+
+ @Override
+ public List findAllStepCMD() {
+ return ctrlFuncDeviceCMDList;
+ }
+
+ @Override
+ public List findAll() {
+ return this.baseMapper.selectList(null);
+ }
+
+ @Override
+ public DeviceCtrlFuncVO findVOById(Long id) {
+ CtrlFunc ctrlFunc = this.getById(id);
+ List ctrlFuncStepList = ctrlFuncStepService.selectListByFuncCmd(ctrlFunc.getFuncCmd());
+ DeviceCtrlFuncVO ctrlFuncVO = new DeviceCtrlFuncVO();
+ ctrlFuncVO.setId(ctrlFunc.getId());
+ ctrlFuncVO.setName(ctrlFunc.getName());
+ ctrlFuncVO.setFuncCmd(ctrlFunc.getFuncCmd());
+ ctrlFuncVO.setFuncCmd(ctrlFunc.getFuncCmd());
+ ctrlFuncVO.setCtrlFuncStepList(ctrlFuncStepList);
+ return ctrlFuncVO;
+ }
+
+ @Override
+ public long countFuncCmdByFuncCmd(String funcCmd) {
+ return this.count(new LambdaQueryWrapper().eq(CtrlFunc::getFuncCmd, funcCmd));
+ }
+
+ @Override
+ public boolean addCtrlFunc(CtrlFuncForm ctrlFuncForm) {
+ CtrlFunc ctrlFunc = new CtrlFunc();
+ ctrlFunc.setFuncCmd(ctrlFuncForm.getFuncCmd());
+ ctrlFunc.setName(ctrlFuncForm.getName());
+ if (this.save(ctrlFunc)) {
+ return reSaveCtrlFunc(ctrlFuncForm, ctrlFunc);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean updateCtrlFunc(Long id, CtrlFuncForm ctrlFuncForm) {
+ CtrlFunc ctrlFunc = this.getById(id);
+ if (ctrlFunc != null) {
+ ctrlFunc.setFuncCmd(ctrlFuncForm.getFuncCmd());
+ ctrlFunc.setName(ctrlFuncForm.getName());
+ if (this.updateById(ctrlFunc)) {
+ return reSaveCtrlFunc(ctrlFuncForm, ctrlFunc);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean deleteCtrlFunc(String idsStr) {
+ List ids = Arrays.stream(idsStr.split(","))
+ .map(Long::parseLong)
+ .collect(Collectors.toList());
+ for (Long id : ids) {
+ String funcCmd = this.getById(id).getFuncCmd();
+ ctrlFuncStepService.remove(new LambdaQueryWrapper().eq(CtrlFuncStep::getFuncCmd, funcCmd));
+ }
+ return this.removeByIds(ids);
+ }
+
+ private boolean reSaveCtrlFunc(CtrlFuncForm ctrlFuncForm, CtrlFunc ctrlFunc) {
+ ctrlFuncStepService.remove(new LambdaQueryWrapper().eq(CtrlFuncStep::getFuncCmd, ctrlFunc.getFuncCmd()));
+ List ctrlFuncStepFormList = ctrlFuncForm.getCtrlFuncStepList();
+ List ctrlFuncStepList = ctrlFuncStepFormList.stream()
+ .map(ctrlFuncStepForm -> {
+ CtrlFuncStep ctrlFuncStep = new CtrlFuncStep();
+ ctrlFuncStep.setFuncCmd(ctrlFunc.getFuncCmd());
+ ctrlFuncStep.setDeviceCmd(ctrlFuncStepForm.getDeviceCmd());
+ ctrlFuncStep.setRemarks(ctrlFuncStepForm.getRemarks());
+ ctrlFuncStep.setParams(JSONUtil.toJsonStr(ctrlFuncStepForm.getParams()));
+ return ctrlFuncStep;
+ }).toList();
+ return ctrlFuncStepService.saveBatch(ctrlFuncStepList);
+ }
+}
diff --git a/src/main/java/com/qyft/ms/device/service/impl/CtrlFuncStepServiceImpl.java b/src/main/java/com/qyft/ms/device/service/impl/CtrlFuncStepServiceImpl.java
new file mode 100644
index 0000000..d333bd6
--- /dev/null
+++ b/src/main/java/com/qyft/ms/device/service/impl/CtrlFuncStepServiceImpl.java
@@ -0,0 +1,20 @@
+package com.qyft.ms.device.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qyft.ms.device.mapper.CtrlFuncStepMapper;
+import com.qyft.ms.device.model.entity.CtrlFuncStep;
+import com.qyft.ms.device.service.ICtrlFuncStepService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class CtrlFuncStepServiceImpl extends ServiceImpl implements ICtrlFuncStepService {
+
+ @Override
+ public List selectListByFuncCmd(String funcCmd) {
+ return this.list(new LambdaQueryWrapper().eq(CtrlFuncStep::getFuncCmd, funcCmd));
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/enums/DirectiveTypeEnum.java b/src/main/java/com/qyft/ms/enums/DirectiveTypeEnum.java
new file mode 100644
index 0000000..833c2f4
--- /dev/null
+++ b/src/main/java/com/qyft/ms/enums/DirectiveTypeEnum.java
@@ -0,0 +1,34 @@
+package com.qyft.ms.enums;
+
+import cn.hutool.core.util.StrUtil;
+
+public enum DirectiveTypeEnum {
+ ADD("add", "抬起托盘");
+
+ private final String typeCode;
+ private final String typeName;
+
+ DirectiveTypeEnum(String typeCode, String typeName) {
+ this.typeCode = typeCode;
+ this.typeName = typeName;
+ }
+
+ public static String getByCode(String code) {
+ if (StrUtil.isNotBlank(code)) {
+ for (DirectiveTypeEnum item : DirectiveTypeEnum.values()) {
+ if (item.typeCode().equals(code.trim())) {
+ return item.typeCode();
+ }
+ }
+ }
+ return null;
+ }
+
+ public String typeCode() {
+ return typeCode;
+ }
+
+ public String typeName() {
+ return typeName;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/generator/CodeGenerator.java b/src/main/java/com/qyft/ms/generator/CodeGenerator.java
new file mode 100644
index 0000000..63468a2
--- /dev/null
+++ b/src/main/java/com/qyft/ms/generator/CodeGenerator.java
@@ -0,0 +1,28 @@
+package com.qyft.ms.generator;
+
+import com.baomidou.mybatisplus.generator.FastAutoGenerator;
+import com.baomidou.mybatisplus.generator.config.OutputFile;
+import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
+
+import java.util.Collections;
+
+public class CodeGenerator {
+ public static void main(String[] args) {
+ FastAutoGenerator.create("jdbc:sqlite:" + System.getProperty("user.dir") + "/matrix-spray.db", "", "")
+ .globalConfig(builder -> {
+ builder.author("qyft") // 设置作者
+ .enableSwagger() // 开启 swagger 模式
+ .outputDir("D://new"); // 指定输出目录
+ })
+ .packageConfig(builder -> {
+ builder.parent("com.qyft.ms") // 设置父包名
+ .moduleName("device") // 设置父包模块名
+ .pathInfo(Collections.singletonMap(OutputFile.xml, "D://new/resources/mapper")); // 设置mapperXml生成路径
+ })
+ .strategyConfig(builder -> {
+ builder.addTablePrefix("t_", "c_"); // 设置过滤表前缀
+ })
+ .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
+ .execute();
+ }
+}
diff --git a/src/main/java/com/qyft/ms/mapper/LogsMapper.java b/src/main/java/com/qyft/ms/mapper/LogsMapper.java
new file mode 100644
index 0000000..ad0f32f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/mapper/LogsMapper.java
@@ -0,0 +1,12 @@
+package com.qyft.ms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qyft.ms.model.entity.Logs;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 日志持久层接口
+ */
+@Mapper
+public interface LogsMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/qyft/ms/mapper/SysSettingsMapper.java b/src/main/java/com/qyft/ms/mapper/SysSettingsMapper.java
new file mode 100644
index 0000000..b0960c5
--- /dev/null
+++ b/src/main/java/com/qyft/ms/mapper/SysSettingsMapper.java
@@ -0,0 +1,29 @@
+package com.qyft.ms.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qyft.ms.model.dto.SysSettingsDTO;
+import com.qyft.ms.model.entity.SysSettings;
+import com.qyft.ms.model.vo.SysSettingVO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+
+/**
+ *
+ * Mapper 接口
+ *
+ *
+ * @author 郭安鹏
+ * @since 2025-02-17
+ */
+
+@Mapper
+public interface SysSettingsMapper extends BaseMapper {
+ List getConfig(String type);
+
+ void updateById(SysSettingsDTO dto);
+
+ int getIdByCode(String type);
+}
+
diff --git a/src/main/java/com/qyft/ms/model/bo/CraftsStepMethod.java b/src/main/java/com/qyft/ms/model/bo/CraftsStepMethod.java
new file mode 100644
index 0000000..3eb335b
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/bo/CraftsStepMethod.java
@@ -0,0 +1,52 @@
+package com.qyft.ms.model.bo;
+
+import lombok.Data;
+
+/**
+ * 工艺步骤执行方法
+ */
+@Data
+public class CraftsStepMethod {
+ /**
+ * 上升托盘
+ */
+ public static final String UP_TRAY = "upTray";
+ /**
+ * 降下托盘
+ */
+ public static final String DOWN_TRAY = "downTray";
+ /**
+ * 加液
+ */
+ public static final String ADD_LIQUID = "addLiquid";
+ /**
+ * 移动到溶液位置
+ */
+ public static final String MOVE_TO_SOL = "moveToSol";
+ /**
+ * 移动到加热器位置
+ */
+ public static final String MOVE_TO_HEAT = "moveToHeat";
+ /**
+ * 摇晃
+ */
+ public static final String SHAKING = "shaking";
+ /**
+ * 开始加热
+ */
+ public static final String START_HEATING = "startHeating";
+ /**
+ * 停止加热
+ */
+ public static final String STOP_HEATING = "stopHeating";
+ /**
+ * 拍照
+ */
+ public static final String TAKE_PHOTO = "takePhoto";
+ /**
+ * 等待
+ */
+ public static final String DELAY = "delay";
+
+
+}
diff --git a/src/main/java/com/qyft/ms/model/bo/CraftsStepStatus.java b/src/main/java/com/qyft/ms/model/bo/CraftsStepStatus.java
new file mode 100644
index 0000000..21cfa85
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/bo/CraftsStepStatus.java
@@ -0,0 +1,35 @@
+package com.qyft.ms.model.bo;
+
+import lombok.Data;
+
+/**
+ * 工艺步骤执行状态
+ */
+@Data
+public class CraftsStepStatus {
+
+ /**
+ * 未执行
+ */
+ public static final int NOT_EXECUTED = 0;
+ /**
+ * 正在执行
+ */
+ public static final int IN_PROGRESS = 1;
+ /**
+ * 暂停执行
+ */
+ public static final int PAUSED = 2;
+ /**
+ * 停止执行
+ */
+ public static final int STOPPED = 3;
+ /**
+ * 执行错误
+ */
+ public static final int ERROR = 4;
+ /**
+ * 执行完成
+ */
+ public static final int FINISH = 6;
+}
diff --git a/src/main/java/com/qyft/ms/model/bo/TubeSol.java b/src/main/java/com/qyft/ms/model/bo/TubeSol.java
new file mode 100644
index 0000000..c0d37d6
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/bo/TubeSol.java
@@ -0,0 +1,23 @@
+package com.qyft.ms.model.bo;
+
+import lombok.Data;
+
+/**
+ * 试管添加溶液
+ */
+@Data
+public class TubeSol {
+ /**
+ * 需要添加溶液的试管编号
+ */
+ private Integer tubeNum;
+ /**
+ * 溶液id
+ */
+ private Long solId;
+ /**
+ * 加液量
+ */
+ private Integer volume;
+
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/BaseDataDTO.java b/src/main/java/com/qyft/ms/model/dto/BaseDataDTO.java
new file mode 100644
index 0000000..93e7f85
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/BaseDataDTO.java
@@ -0,0 +1,14 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class BaseDataDTO {
+ @Schema(description = "加热区id")
+ private Long id;
+ @Schema(description = "温度")
+ private Long temperature;
+ @Schema(description = "工艺id")
+ private Long craftId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/CmdInjectFluidDTO.java b/src/main/java/com/qyft/ms/model/dto/CmdInjectFluidDTO.java
new file mode 100644
index 0000000..6f7986d
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/CmdInjectFluidDTO.java
@@ -0,0 +1,13 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class CmdInjectFluidDTO {
+ @Schema(description = "指令id")
+ private String commandId;
+
+ @Schema(description = "加液数据list")
+ private InjectFluid[] injectFluids;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/ContainerDTO.java b/src/main/java/com/qyft/ms/model/dto/ContainerDTO.java
new file mode 100644
index 0000000..75441cd
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/ContainerDTO.java
@@ -0,0 +1,14 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class ContainerDTO {
+ @Schema(description = "容器id")
+ private Long id;
+ @Schema(description = "溶液id")
+ private Long solutionId;
+ @Schema(description = "已使用容量")
+ private Integer capacityUsed;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/InjectFluid.java b/src/main/java/com/qyft/ms/model/dto/InjectFluid.java
new file mode 100644
index 0000000..192216f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/InjectFluid.java
@@ -0,0 +1,14 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class InjectFluid {
+ @Schema(description = "试管编号")
+ private Integer tubeNum;
+ @Schema(description = "溶液id")
+ private Integer solutionId;
+ @Schema(description = "容量")
+ private Integer volume;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/LogDTO.java b/src/main/java/com/qyft/ms/model/dto/LogDTO.java
new file mode 100644
index 0000000..3533b3b
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/LogDTO.java
@@ -0,0 +1,10 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class LogDTO {
+ @Schema(description = "日志信息")
+ private String text;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/PauseCraftsDto.java b/src/main/java/com/qyft/ms/model/dto/PauseCraftsDto.java
new file mode 100644
index 0000000..d4a5914
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/PauseCraftsDto.java
@@ -0,0 +1,11 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "暂停执行工艺")
+@Data
+public class PauseCraftsDto {
+ @Schema(description = "加热区id")
+ private String heatId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/ResumeCraftsDto.java b/src/main/java/com/qyft/ms/model/dto/ResumeCraftsDto.java
new file mode 100644
index 0000000..d3da8a6
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/ResumeCraftsDto.java
@@ -0,0 +1,11 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "恢复执行工艺")
+@Data
+public class ResumeCraftsDto {
+ @Schema(description = "加热区id")
+ private String heatId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/StartCraftsDTO.java b/src/main/java/com/qyft/ms/model/dto/StartCraftsDTO.java
new file mode 100644
index 0000000..11c40fb
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/StartCraftsDTO.java
@@ -0,0 +1,13 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "开始工艺")
+@Data
+public class StartCraftsDTO {
+ @Schema(description = "工艺id")
+ private Long craftId;
+ @Schema(description = "加热区id")
+ private String heatId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/StartHeatDTO.java b/src/main/java/com/qyft/ms/model/dto/StartHeatDTO.java
new file mode 100644
index 0000000..449c038
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/StartHeatDTO.java
@@ -0,0 +1,13 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class StartHeatDTO {
+ @Schema(description = "指令id")
+ private String commandId;
+
+ @Schema(description = "加液数据list")
+ private Integer[] heatIds;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/StopCraftsDto.java b/src/main/java/com/qyft/ms/model/dto/StopCraftsDto.java
new file mode 100644
index 0000000..051fb51
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/StopCraftsDto.java
@@ -0,0 +1,11 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "恢复执行工艺")
+@Data
+public class StopCraftsDto {
+ @Schema(description = "加热区id")
+ private String heatId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/StopTaskDTO.java b/src/main/java/com/qyft/ms/model/dto/StopTaskDTO.java
new file mode 100644
index 0000000..07c4538
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/StopTaskDTO.java
@@ -0,0 +1,10 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class StopTaskDTO {
+ @Schema(description = "实验id")
+ private Long taskId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/SysSettingsDTO.java b/src/main/java/com/qyft/ms/model/dto/SysSettingsDTO.java
new file mode 100644
index 0000000..f1c45ef
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/SysSettingsDTO.java
@@ -0,0 +1,16 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class SysSettingsDTO {
+ @Schema(description = "主键id")
+ private Long id;
+ @Schema(description = "值")
+ private String value;
+ @Schema(description = "温度")
+ private Long temperature;
+ @Schema(description = "工艺id")
+ private Long craftId;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/TaskDTO.java b/src/main/java/com/qyft/ms/model/dto/TaskDTO.java
new file mode 100644
index 0000000..890c697
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/TaskDTO.java
@@ -0,0 +1,10 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class TaskDTO {
+ @Schema(description = "实验名称")
+ private String name;
+}
diff --git a/src/main/java/com/qyft/ms/model/dto/WebsocketDTO.java b/src/main/java/com/qyft/ms/model/dto/WebsocketDTO.java
new file mode 100644
index 0000000..b380586
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/dto/WebsocketDTO.java
@@ -0,0 +1,22 @@
+package com.qyft.ms.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class WebsocketDTO {
+
+ @Schema(description = "指令id,不指定后台会自动生成uuid")
+ private String commandId;
+
+ @NotBlank()
+ @Schema(description = "指令类型", example = "upTray")
+ private String commandName;
+
+ @Schema(description = "参数")
+ private Map params;
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/Container.java b/src/main/java/com/qyft/ms/model/entity/Container.java
new file mode 100644
index 0000000..3a83cd8
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/Container.java
@@ -0,0 +1,35 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "容器表")
+@TableName("container")
+@Data
+public class Container {
+ @NotBlank
+ @Schema(description = "容器id")
+ private Long id;
+
+ @NotBlank
+ @Schema(description = "类型 0 溶液 1 废液")
+ private Long type;
+
+ @Schema(description = "溶液id")
+ private Long solutionId;
+
+ @Schema(description = "机器id")
+ private String pumpId;
+
+ @NotBlank
+ @Schema(description = "总容量")
+ private Integer capacityTotal;
+
+ @NotBlank
+ @Schema(description = "使用容量")
+ private Integer capacityUsed;
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/Crafts.java b/src/main/java/com/qyft/ms/model/entity/Crafts.java
new file mode 100644
index 0000000..d600bb3
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/Crafts.java
@@ -0,0 +1,25 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "工艺")
+@TableName("crafts")
+@Data
+public class Crafts extends BaseEntity {
+
+ @NotBlank
+ @Schema(description = "工艺名称")
+ private String name;
+
+ @Schema(description = "工艺步骤")
+ private String steps;
+
+ @Schema(description = "矿石ID")
+ private Long oresId;
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/Logs.java b/src/main/java/com/qyft/ms/model/entity/Logs.java
new file mode 100644
index 0000000..5011ce8
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/Logs.java
@@ -0,0 +1,30 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "日志")
+@TableName("logs")
+@Data
+public class Logs {
+
+ @NotBlank
+ @Schema(description = "id")
+ private Long id;
+
+ @NotBlank
+ @Schema(description = "创建人")
+ private Long createUser;
+
+ @NotBlank
+ @Schema(description = "日志内容")
+ private String text;
+
+ @NotBlank
+ @Schema(description = "创建时间")
+ private String createTime;
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/Ores.java b/src/main/java/com/qyft/ms/model/entity/Ores.java
new file mode 100644
index 0000000..0de00ad
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/Ores.java
@@ -0,0 +1,20 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "矿石")
+@TableName("ores")
+@Data
+public class Ores extends BaseEntity {
+
+ @NotBlank()
+ @Schema(description = "矿石名称")
+ private String name;
+
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/Solutions.java b/src/main/java/com/qyft/ms/model/entity/Solutions.java
new file mode 100644
index 0000000..eb26aa5
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/Solutions.java
@@ -0,0 +1,20 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "溶液")
+@TableName("solutions")
+@Data
+public class Solutions extends BaseEntity {
+
+ @NotBlank()
+ @Schema(description = "溶液名称")
+ private String name;
+
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/SysSettings.java b/src/main/java/com/qyft/ms/model/entity/SysSettings.java
new file mode 100644
index 0000000..c9f0cf7
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/SysSettings.java
@@ -0,0 +1,43 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ *
+ * 系统配置
+ *
+ *
+ * @author 郭安鹏
+ * @since 2025-02-17
+ */
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "系统配置")
+@TableName("sys_settings")
+@Data
+public class SysSettings {
+
+ @Schema(description = "主键id")
+ private Long id;
+
+ @Schema(description = "父id")
+ private Long parentId;
+
+ @Schema(description = "名称")
+ private String name;
+
+ @Schema(description = "code")
+ private String code;
+
+ @Schema(description = "配置值")
+ private String value1;
+
+ @Schema(description = "配置值")
+ private String value2;
+
+ @Schema(description = "配置值")
+ private String value3;
+
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/TaskSteps.java b/src/main/java/com/qyft/ms/model/entity/TaskSteps.java
new file mode 100644
index 0000000..e307e91
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/TaskSteps.java
@@ -0,0 +1,30 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "实验步骤")
+@TableName("task_steps")
+@Data
+public class TaskSteps {
+
+ @NotBlank
+ @Schema(description = "id")
+ private Long id;
+
+ @NotBlank
+ @Schema(description = "实验id")
+ private Long taskId;
+
+ @NotBlank
+ @Schema(description = "步骤描述")
+ private String stepDescription;
+
+ @NotBlank
+ @Schema(description = "创建时间")
+ private String createTime;
+}
diff --git a/src/main/java/com/qyft/ms/model/entity/Tasks.java b/src/main/java/com/qyft/ms/model/entity/Tasks.java
new file mode 100644
index 0000000..d66f85c
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/entity/Tasks.java
@@ -0,0 +1,35 @@
+package com.qyft.ms.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "实验")
+@TableName("tasks")
+@Data
+public class Tasks extends BaseEntity {
+
+ @NotBlank
+ @Schema(description = "实验名称")
+ private String name;
+
+ @NotBlank
+ @Schema(description = "开始时间")
+ private String startTime;
+
+ @Schema(description = "创建人")
+ private Long createUser;
+
+ @Schema(description = "结束时间")
+ private String endTime;
+
+ @Schema(description = "状态 1 执行中 2 执行完毕")
+ private Integer status;
+
+ @Schema(description = "是否删除 0 未删除 1 已删除")
+ private Integer isDeleted;
+}
diff --git a/src/main/java/com/qyft/ms/model/form/CMDForm.java b/src/main/java/com/qyft/ms/model/form/CMDForm.java
new file mode 100644
index 0000000..ac29491
--- /dev/null
+++ b/src/main/java/com/qyft/ms/model/form/CMDForm.java
@@ -0,0 +1,23 @@
+package com.qyft.ms.model.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "指令")
+@Data
+public class CMDForm {
+
+ @Schema(description = "指令id,不指定后台会自动生成uuid")
+ private String commandId;
+
+ @NotBlank()
+ @Schema(description = "指令类型", example = "upTray")
+ private String command;
+
+ @Schema(description = "参数")
+ private List
+ */
+@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;
+
+}
diff --git a/src/main/java/com/qyft/ms/system/common/base/BasePageQuery.java b/src/main/java/com/qyft/ms/system/common/base/BasePageQuery.java
new file mode 100644
index 0000000..92abfa7
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/base/BasePageQuery.java
@@ -0,0 +1,26 @@
+package com.qyft.ms.system.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;
+
+
+}
diff --git a/src/main/java/com/qyft/ms/system/common/base/IBaseEnum.java b/src/main/java/com/qyft/ms/system/common/base/IBaseEnum.java
new file mode 100644
index 0000000..ce3bc1b
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/base/IBaseEnum.java
@@ -0,0 +1,84 @@
+package com.qyft.ms.system.common.base;
+
+
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.EnumSet;
+import java.util.Objects;
+
+/**
+ * 枚举通用接口
+ */
+public interface IBaseEnum {
+
+ /**
+ * 根据值获取枚举
+ *
+ * @param value
+ * @param clazz
+ * @param 枚举
+ * @return
+ */
+ static & IBaseEnum> E getEnumByValue(Object value, Class clazz) {
+ Objects.requireNonNull(value);
+ EnumSet 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
+ * @return
+ */
+ static & IBaseEnum> String getLabelByValue(Object value, Class clazz) {
+ Objects.requireNonNull(value);
+ EnumSet 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
+ * @return
+ */
+ static & IBaseEnum> Object getValueByLabel(String label, Class clazz) {
+ Objects.requireNonNull(label);
+ EnumSet 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;
+ }
+
+ T getValue();
+
+ String getLabel();
+
+
+}
diff --git a/src/main/java/com/qyft/ms/system/common/enums/DeletedEnum.java b/src/main/java/com/qyft/ms/system/common/enums/DeletedEnum.java
new file mode 100644
index 0000000..a10f4e4
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/enums/DeletedEnum.java
@@ -0,0 +1,24 @@
+package com.qyft.ms.system.common.enums;
+
+import com.qyft.ms.system.common.base.IBaseEnum;
+import lombok.Getter;
+
+/**
+ * 删除状态枚举
+ */
+@Getter
+public enum DeletedEnum implements IBaseEnum {
+
+ ENABLE(1, "删除"),
+ DISABLE(0, "未删除");
+
+ private final Integer value;
+
+
+ private final String label;
+
+ DeletedEnum(Integer value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/system/common/enums/StatusEnum.java b/src/main/java/com/qyft/ms/system/common/enums/StatusEnum.java
new file mode 100644
index 0000000..715f16a
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/enums/StatusEnum.java
@@ -0,0 +1,24 @@
+package com.qyft.ms.system.common.enums;
+
+import com.qyft.ms.system.common.base.IBaseEnum;
+import lombok.Getter;
+
+/**
+ * 状态枚举
+ */
+@Getter
+public enum StatusEnum implements IBaseEnum {
+
+ ENABLE(1, "启用"),
+ DISABLE(0, "禁用");
+
+ private final Integer value;
+
+
+ private final String label;
+
+ StatusEnum(Integer value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/system/common/result/IResultCode.java b/src/main/java/com/qyft/ms/system/common/result/IResultCode.java
new file mode 100644
index 0000000..b3ea2a3
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/result/IResultCode.java
@@ -0,0 +1,12 @@
+package com.qyft.ms.system.common.result;
+
+/**
+ * 响应码接口
+ **/
+public interface IResultCode {
+
+ String getCode();
+
+ String getMsg();
+
+}
diff --git a/src/main/java/com/qyft/ms/system/common/result/PageResult.java b/src/main/java/com/qyft/ms/system/common/result/PageResult.java
new file mode 100644
index 0000000..87a52a6
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/result/PageResult.java
@@ -0,0 +1,43 @@
+package com.qyft.ms.system.common.result;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分页响应结构体
+ */
+@Data
+public class PageResult implements Serializable {
+
+ private String code;
+
+ private Data data;
+
+ private String msg;
+
+ public static PageResult success(IPage page) {
+ PageResult result = new PageResult<>();
+ result.setCode(ResultCode.SUCCESS.getCode());
+
+ Data 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 {
+
+ private List list;
+
+ private long total;
+
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/system/common/result/Result.java b/src/main/java/com/qyft/ms/system/common/result/Result.java
new file mode 100644
index 0000000..e7bf1ec
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/result/Result.java
@@ -0,0 +1,71 @@
+package com.qyft.ms.system.common.result;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 统一响应结构体
+ **/
+@Data
+public class Result implements Serializable {
+
+ private String code;
+
+ private T data;
+
+ private String msg;
+
+ public static Result success() {
+ return success(null);
+ }
+
+ public static Result success(T data) {
+ Result result = new Result<>();
+ result.setCode(ResultCode.SUCCESS.getCode());
+ result.setMsg(ResultCode.SUCCESS.getMsg());
+ result.setData(data);
+ return result;
+ }
+
+ public static Result failed() {
+ return result(ResultCode.SYSTEM_ERROR.getCode(), ResultCode.SYSTEM_ERROR.getMsg(), null);
+ }
+
+ public static Result failed(String msg) {
+ return result(ResultCode.SYSTEM_ERROR.getCode(), msg, null);
+ }
+
+ public static Result judge(boolean status) {
+ if (status) {
+ return success();
+ } else {
+ return failed();
+ }
+ }
+
+ public static Result failed(IResultCode resultCode) {
+ return result(resultCode.getCode(), resultCode.getMsg(), null);
+ }
+
+ public static Result failed(IResultCode resultCode, String msg) {
+ return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null);
+ }
+
+ private static Result result(IResultCode resultCode, T data) {
+ return result(resultCode.getCode(), resultCode.getMsg(), data);
+ }
+
+ private static Result result(String code, String msg, T data) {
+ Result 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());
+ }
+}
diff --git a/src/main/java/com/qyft/ms/system/common/result/ResultCode.java b/src/main/java/com/qyft/ms/system/common/result/ResultCode.java
new file mode 100644
index 0000000..e422009
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/result/ResultCode.java
@@ -0,0 +1,328 @@
+package com.qyft.ms.system.common.result;
+
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 响应码枚举
+ **/
+@AllArgsConstructor
+@NoArgsConstructor
+public enum ResultCode implements IResultCode, Serializable {
+
+ SUCCESS("00000", "ok"),
+
+ /**
+ * 一级宏观错误码
+ */
+ USER_ERROR("A0001", "用户端错误"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_REGISTRATION_ERROR("A0100", "用户注册错误"),
+ USER_NOT_AGREE_PRIVACY_AGREEMENT("A0101", "用户未同意隐私协议"),
+ REGISTRATION_COUNTRY_OR_REGION_RESTRICTED("A0102", "注册国家或地区受限"),
+
+ USERNAME_VERIFICATION_FAILED("A0110", "用户名校验失败"),
+ USERNAME_ALREADY_EXISTS("A0111", "用户名已存在"),
+ USERNAME_CONTAINS_SENSITIVE_WORDS("A0112", "用户名包含敏感词"),
+ USERNAME_CONTAINS_SPECIAL_CHARACTERS("A0113", "用户名包含特殊字符"),
+
+ PASSWORD_VERIFICATION_FAILED("A0120", "密码校验失败"),
+ PASSWORD_LENGTH_NOT_ENOUGH("A0121", "密码长度不够"),
+ PASSWORD_STRENGTH_NOT_ENOUGH("A0122", "密码强度不够"),
+
+ VERIFICATION_CODE_INPUT_ERROR("A0130", "校验码输入错误"),
+ SMS_VERIFICATION_CODE_INPUT_ERROR("A0131", "短信校验码输入错误"),
+ EMAIL_VERIFICATION_CODE_INPUT_ERROR("A0132", "邮件校验码输入错误"),
+ VOICE_VERIFICATION_CODE_INPUT_ERROR("A0133", "语音校验码输入错误"),
+
+ USER_CERTIFICATE_EXCEPTION("A0140", "用户证件异常"),
+ USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证件类型未选择"),
+ MAINLAND_ID_NUMBER_VERIFICATION_ILLEGAL("A0142", "大陆身份证编号校验非法"),
+
+ USER_BASIC_INFORMATION_VERIFICATION_FAILED("A0150", "用户基本信息校验失败"),
+ PHONE_FORMAT_VERIFICATION_FAILED("A0151", "手机格式校验失败"),
+ ADDRESS_FORMAT_VERIFICATION_FAILED("A0152", "地址格式校验失败"),
+ EMAIL_FORMAT_VERIFICATION_FAILED("A0153", "邮箱格式校验失败"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_LOGIN_EXCEPTION("A0200", "用户登录异常"),
+ USER_ACCOUNT_FROZEN("A0201", "用户账户被冻结"),
+ USER_ACCOUNT_ABOLISHED("A0202", "用户账户已作废"),
+
+ USER_PASSWORD_ERROR("A0210", "用户名或密码错误"),
+ USER_INPUT_PASSWORD_ERROR_LIMIT_EXCEEDED("A0211", "用户输入密码错误次数超限"),
+
+ USER_IDENTITY_VERIFICATION_FAILED("A0220", "用户身份校验失败"),
+ USER_FINGERPRINT_RECOGNITION_FAILED("A0221", "用户指纹识别失败"),
+ USER_FACE_RECOGNITION_FAILED("A0222", "用户面容识别失败"),
+ USER_NOT_AUTHORIZED_THIRD_PARTY_LOGIN("A0223", "用户未获得第三方登录授权"),
+
+ ACCESS_TOKEN_INVALID("A0230", "访问令牌无效或已过期"),
+ REFRESH_TOKEN_INVALID("A0231", "刷新令牌无效或已过期"),
+
+ // 验证码错误
+ USER_VERIFICATION_CODE_ERROR("A0240", "用户验证码错误"),
+ USER_VERIFICATION_CODE_ATTEMPT_LIMIT_EXCEEDED("A0241", "用户验证码尝试次数超限"),
+ USER_VERIFICATION_CODE_EXPIRED("A0242", "用户验证码过期"),
+
+ /**
+ * 二级宏观错误码
+ */
+ ACCESS_PERMISSION_EXCEPTION("A0300", "访问权限异常"),
+ ACCESS_UNAUTHORIZED("A0301", "访问未授权"),
+ AUTHORIZATION_IN_PROGRESS("A0302", "正在授权中"),
+ USER_AUTHORIZATION_APPLICATION_REJECTED("A0303", "用户授权申请被拒绝"),
+
+ ACCESS_OBJECT_PRIVACY_SETTINGS_BLOCKED("A0310", "因访问对象隐私设置被拦截"),
+ AUTHORIZATION_EXPIRED("A0311", "授权已过期"),
+ NO_PERMISSION_TO_USE_API("A0312", "无权限使用 API"),
+
+ USER_ACCESS_BLOCKED("A0320", "用户访问被拦截"),
+ BLACKLISTED_USER("A0321", "黑名单用户"),
+ ACCOUNT_FROZEN("A0322", "账号被冻结"),
+ ILLEGAL_IP_ADDRESS("A0323", "非法 IP 地址"),
+ GATEWAY_ACCESS_RESTRICTED("A0324", "网关访问受限"),
+ REGION_BLACKLIST("A0325", "地域黑名单"),
+
+ SERVICE_ARREARS("A0330", "服务已欠费"),
+
+ USER_SIGNATURE_EXCEPTION("A0340", "用户签名异常"),
+ RSA_SIGNATURE_ERROR("A0341", "RSA 签名错误"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_REQUEST_PARAMETER_ERROR("A0400", "用户请求参数错误"),
+ CONTAINS_ILLEGAL_MALICIOUS_REDIRECT_LINK("A0401", "包含非法恶意跳转链接"),
+ INVALID_USER_INPUT("A0402", "无效的用户输入"),
+
+ REQUEST_REQUIRED_PARAMETER_IS_EMPTY("A0410", "请求必填参数为空"),
+
+ REQUEST_PARAMETER_VALUE_EXCEEDS_ALLOWED_RANGE("A0420", "请求参数值超出允许的范围"),
+ PARAMETER_FORMAT_MISMATCH("A0421", "参数格式不匹配"),
+
+ USER_INPUT_CONTENT_ILLEGAL("A0430", "用户输入内容非法"),
+ CONTAINS_PROHIBITED_SENSITIVE_WORDS("A0431", "包含违禁敏感词"),
+
+ USER_OPERATION_EXCEPTION("A0440", "用户操作异常"),
+
+ DATA_ALREADY_EXISTS("C0450", "数据已存在"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_REQUEST_SERVICE_EXCEPTION("A0500", "用户请求服务异常"),
+ REQUEST_LIMIT_EXCEEDED("A0501", "请求次数超出限制"),
+ REQUEST_CONCURRENCY_LIMIT_EXCEEDED("A0502", "请求并发数超出限制"),
+ USER_OPERATION_PLEASE_WAIT("A0503", "用户操作请等待"),
+ WEBSOCKET_CONNECTION_EXCEPTION("A0504", "WebSocket 连接异常"),
+ WEBSOCKET_CONNECTION_DISCONNECTED("A0505", "WebSocket 连接断开"),
+ USER_DUPLICATE_REQUEST("A0506", "请求过于频繁,请稍后再试。"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_RESOURCE_EXCEPTION("A0600", "用户资源异常"),
+ ACCOUNT_BALANCE_INSUFFICIENT("A0601", "账户余额不足"),
+ USER_DISK_SPACE_INSUFFICIENT("A0602", "用户磁盘空间不足"),
+ USER_MEMORY_SPACE_INSUFFICIENT("A0603", "用户内存空间不足"),
+ USER_OSS_CAPACITY_INSUFFICIENT("A0604", "用户 OSS 容量不足"),
+ USER_QUOTA_EXHAUSTED("A0605", "用户配额已用光"),
+ USER_RESOURCE_NOT_FOUND("A0606", "用户资源不存在"),
+
+ /**
+ * 二级宏观错误码
+ */
+ UPLOAD_FILE_EXCEPTION("A0700", "上传文件异常"),
+ UPLOAD_FILE_TYPE_MISMATCH("A0701", "上传文件类型不匹配"),
+ UPLOAD_FILE_TOO_LARGE("A0702", "上传文件太大"),
+ UPLOAD_IMAGE_TOO_LARGE("A0703", "上传图片太大"),
+ UPLOAD_VIDEO_TOO_LARGE("A0704", "上传视频太大"),
+ UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "上传压缩文件太大"),
+
+ DELETE_FILE_EXCEPTION("A0710", "删除文件异常"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_CURRENT_VERSION_EXCEPTION("A0800", "用户当前版本异常"),
+ USER_INSTALLED_VERSION_NOT_MATCH_SYSTEM("A0801", "用户安装版本与系统不匹配"),
+ USER_INSTALLED_VERSION_TOO_LOW("A0802", "用户安装版本过低"),
+ USER_INSTALLED_VERSION_TOO_HIGH("A0803", "用户安装版本过高"),
+ USER_INSTALLED_VERSION_EXPIRED("A0804", "用户安装版本已过期"),
+ USER_API_REQUEST_VERSION_NOT_MATCH("A0805", "用户 API 请求版本不匹配"),
+ USER_API_REQUEST_VERSION_TOO_HIGH("A0806", "用户 API 请求版本过高"),
+ USER_API_REQUEST_VERSION_TOO_LOW("A0807", "用户 API 请求版本过低"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_PRIVACY_NOT_AUTHORIZED("A0900", "用户隐私未授权"),
+ USER_PRIVACY_NOT_SIGNED("A0901", "用户隐私未签署"),
+ USER_CAMERA_NOT_AUTHORIZED("A0903", "用户相机未授权"),
+ USER_PHOTO_LIBRARY_NOT_AUTHORIZED("A0904", "用户图片库未授权"),
+ USER_FILE_NOT_AUTHORIZED("A0905", "用户文件未授权"),
+ USER_LOCATION_INFORMATION_NOT_AUTHORIZED("A0906", "用户位置信息未授权"),
+ USER_CONTACTS_NOT_AUTHORIZED("A0907", "用户通讯录未授权"),
+
+ /**
+ * 二级宏观错误码
+ */
+ USER_DEVICE_EXCEPTION("A1000", "用户设备异常"),
+ USER_CAMERA_EXCEPTION("A1001", "用户相机异常"),
+ USER_MICROPHONE_EXCEPTION("A1002", "用户麦克风异常"),
+ USER_EARPIECE_EXCEPTION("A1003", "用户听筒异常"),
+ USER_SPEAKER_EXCEPTION("A1004", "用户扬声器异常"),
+ USER_GPS_POSITIONING_EXCEPTION("A1005", "用户 GPS 定位异常"),
+
+ /**
+ * 一级宏观错误码
+ */
+ SYSTEM_ERROR("B0001", "系统执行出错"),
+
+ /**
+ * 二级宏观错误码
+ */
+ SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
+
+ /**
+ * 二级宏观错误码
+ */
+ SYSTEM_DISASTER_RECOVERY_FUNCTION_TRIGGERED("B0200", "系统容灾功能被触发"),
+
+ SYSTEM_RATE_LIMITING("B0210", "系统限流"),
+
+ SYSTEM_FUNCTION_DEGRADATION("B0220", "系统功能降级"),
+
+ /**
+ * 二级宏观错误码
+ */
+ SYSTEM_RESOURCE_EXCEPTION("B0300", "系统资源异常"),
+ SYSTEM_RESOURCE_EXHAUSTED("B0310", "系统资源耗尽"),
+ SYSTEM_DISK_SPACE_EXHAUSTED("B0311", "系统磁盘空间耗尽"),
+ SYSTEM_MEMORY_EXHAUSTED("B0312", "系统内存耗尽"),
+ FILE_HANDLE_EXHAUSTED("B0313", "文件句柄耗尽"),
+ SYSTEM_CONNECTION_POOL_EXHAUSTED("B0314", "系统连接池耗尽"),
+ SYSTEM_THREAD_POOL_EXHAUSTED("B0315", "系统线程池耗尽"),
+
+ SYSTEM_RESOURCE_ACCESS_EXCEPTION("B0320", "系统资源访问异常"),
+ SYSTEM_READ_DISK_FILE_FAILED("B0321", "系统读取磁盘文件失败"),
+
+
+ /**
+ * 一级宏观错误码
+ */
+ THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
+
+ /**
+ * 二级宏观错误码
+ */
+ MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错"),
+
+ RPC_SERVICE_ERROR("C0110", "RPC 服务出错"),
+ RPC_SERVICE_NOT_FOUND("C0111", "RPC 服务未找到"),
+ RPC_SERVICE_NOT_REGISTERED("C0112", "RPC 服务未注册"),
+ INTERFACE_NOT_EXIST("C0113", "接口不存在"),
+
+ MESSAGE_SERVICE_ERROR("C0120", "消息服务出错"),
+ MESSAGE_DELIVERY_ERROR("C0121", "消息投递出错"),
+ MESSAGE_CONSUMPTION_ERROR("C0122", "消息消费出错"),
+ MESSAGE_SUBSCRIPTION_ERROR("C0123", "消息订阅出错"),
+ MESSAGE_GROUP_NOT_FOUND("C0124", "消息分组未查到"),
+
+ CACHE_SERVICE_ERROR("C0130", "缓存服务出错"),
+ KEY_LENGTH_EXCEEDS_LIMIT("C0131", "key 长度超过限制"),
+ VALUE_LENGTH_EXCEEDS_LIMIT("C0132", "value 长度超过限制"),
+ STORAGE_CAPACITY_FULL("C0133", "存储容量已满"),
+ UNSUPPORTED_DATA_FORMAT("C0134", "不支持的数据格式"),
+
+ CONFIGURATION_SERVICE_ERROR("C0140", "配置服务出错"),
+
+ NETWORK_RESOURCE_SERVICE_ERROR("C0150", "网络资源服务出错"),
+ VPN_SERVICE_ERROR("C0151", "VPN 服务出错"),
+ CDN_SERVICE_ERROR("C0152", "CDN 服务出错"),
+ DOMAIN_NAME_RESOLUTION_SERVICE_ERROR("C0153", "域名解析服务出错"),
+ GATEWAY_SERVICE_ERROR("C0154", "网关服务出错"),
+
+ /**
+ * 二级宏观错误码
+ */
+ THIRD_PARTY_SYSTEM_EXECUTION_TIMEOUT("C0200", "第三方系统执行超时"),
+
+ RPC_EXECUTION_TIMEOUT("C0210", "RPC 执行超时"),
+
+ MESSAGE_DELIVERY_TIMEOUT("C0220", "消息投递超时"),
+
+ CACHE_SERVICE_TIMEOUT("C0230", "缓存服务超时"),
+
+ CONFIGURATION_SERVICE_TIMEOUT("C0240", "配置服务超时"),
+
+ DATABASE_SERVICE_TIMEOUT("C0250", "数据库服务超时"),
+
+ /**
+ * 二级宏观错误码
+ */
+ DATABASE_SERVICE_ERROR("C0300", "数据库服务出错"),
+
+ TABLE_NOT_EXIST("C0311", "表不存在"),
+ COLUMN_NOT_EXIST("C0312", "列不存在"),
+
+ MULTIPLE_SAME_NAME_COLUMNS_IN_MULTI_TABLE_ASSOCIATION("C0321", "多表关联中存在多个相同名称的列"),
+
+ DATABASE_DEADLOCK("C0331", "数据库死锁"),
+
+ PRIMARY_KEY_CONFLICT("C0341", "主键冲突"),
+
+ /**
+ * 二级宏观错误码
+ */
+ THIRD_PARTY_DISASTER_RECOVERY_SYSTEM_TRIGGERED("C0400", "第三方容灾系统被触发"),
+ THIRD_PARTY_SYSTEM_RATE_LIMITING("C0401", "第三方系统限流"),
+ THIRD_PARTY_FUNCTION_DEGRADATION("C0402", "第三方功能降级"),
+
+ /**
+ * 二级宏观错误码
+ */
+ NOTIFICATION_SERVICE_ERROR("C0500", "通知服务出错"),
+ SMS_REMINDER_SERVICE_FAILED("C0501", "短信提醒服务失败"),
+ VOICE_REMINDER_SERVICE_FAILED("C0502", "语音提醒服务失败"),
+ EMAIL_REMINDER_SERVICE_FAILED("C0503", "邮件提醒服务失败");
+
+
+ private String code;
+ private String msg;
+
+ public static ResultCode getValue(String code) {
+ for (ResultCode value : values()) {
+ if (value.getCode().equals(code)) {
+ return value;
+ }
+ }
+ return SYSTEM_ERROR; // 默认系统执行错误
+ }
+
+ @Override
+ public String getCode() {
+ return code;
+ }
+
+ @Override
+ public String getMsg() {
+ return msg;
+ }
+
+ @Override
+ public String toString() {
+ return "{" +
+ "\"code\":\"" + code + '\"' +
+ ", \"msg\":\"" + msg + '\"' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/common/utils/JwtUtil.java b/src/main/java/com/qyft/ms/system/common/utils/JwtUtil.java
new file mode 100644
index 0000000..27fc504
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/utils/JwtUtil.java
@@ -0,0 +1,44 @@
+package com.qyft.ms.system.common.utils;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+public class JwtUtil {
+ public static final String secret = "mySecretKeyForJwtGenerationAaron";
+
+ // 生成加密的 JWT,称为 JWE
+ public static String createJWE(String subject) {
+ long nowMillis = System.currentTimeMillis();
+ Date now = new Date(nowMillis);
+ SecretKey secretKey = getKeyFromSecret(secret);
+ return Jwts.builder()
+ .issuedAt(now) // 设置发行时间
+ .subject(subject) // 设置主题(用户)
+ .encryptWith(secretKey, Jwts.ENC.A128CBC_HS256) // 使用密钥签名
+ .compact(); // 生成 JWE
+ }
+
+ // 验证 JWE
+ public static Claims parseJWE(String jwe) {
+ try {
+ SecretKey secretKey = getKeyFromSecret(secret);
+ return Jwts.parser()
+ .decryptWith(secretKey) // 设置验证签名的密钥
+ .build()
+ .parseEncryptedClaims(jwe).getPayload();
+ } catch (JwtException e) {
+ return null;
+ }
+ }
+
+ // 将密钥原文转为 Key
+ public static SecretKey getKeyFromSecret(String secret) {
+ return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/common/utils/ResponseUtils.java b/src/main/java/com/qyft/ms/system/common/utils/ResponseUtils.java
new file mode 100644
index 0000000..739e781
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/utils/ResponseUtils.java
@@ -0,0 +1,80 @@
+package com.qyft.ms.system.common.utils;
+
+import cn.hutool.json.JSONUtil;
+import com.qyft.ms.system.common.result.Result;
+import com.qyft.ms.system.common.result.ResultCode;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 响应工具类
+ */
+@Slf4j
+public class ResponseUtils {
+
+
+ /**
+ * 异常消息返回(适用过滤器中处理异常响应)
+ *
+ * @param response HttpServletResponse
+ * @param resultCode 响应结果码
+ */
+ public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode) {
+ int status = getHttpStatus(resultCode);
+
+ response.setStatus(status);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+
+ try (PrintWriter writer = response.getWriter()) {
+ String jsonResponse = JSONUtil.toJsonStr(Result.failed(resultCode));
+ writer.print(jsonResponse);
+ writer.flush(); // 确保将响应内容写入到输出流
+ } catch (IOException e) {
+ log.error("响应异常处理失败", e);
+ }
+ }
+
+ /**
+ * 异常消息返回(适用过滤器中处理异常响应)
+ *
+ * @param response HttpServletResponse
+ * @param resultCode 响应结果码
+ */
+ public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode, String message) {
+ int status = getHttpStatus(resultCode);
+
+ response.setStatus(status);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+
+ try (PrintWriter writer = response.getWriter()) {
+ String jsonResponse = JSONUtil.toJsonStr(Result.failed(resultCode, message));
+ writer.print(jsonResponse);
+ writer.flush(); // 确保将响应内容写入到输出流
+ } catch (IOException e) {
+ log.error("响应异常处理失败", e);
+ }
+ }
+
+
+ /**
+ * 根据结果码获取HTTP状态码
+ *
+ * @param resultCode 结果码
+ * @return HTTP状态码
+ */
+ private static int getHttpStatus(ResultCode resultCode) {
+ return switch (resultCode) {
+ case ACCESS_UNAUTHORIZED, ACCESS_TOKEN_INVALID, REFRESH_TOKEN_INVALID -> HttpStatus.UNAUTHORIZED.value();
+ default -> HttpStatus.BAD_REQUEST.value();
+ };
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/system/config/FilterConfig.java b/src/main/java/com/qyft/ms/system/config/FilterConfig.java
new file mode 100644
index 0000000..39f2164
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/config/FilterConfig.java
@@ -0,0 +1,23 @@
+package com.qyft.ms.system.config;
+
+import com.qyft.ms.system.filter.JwtAuthenticationFilter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+public class FilterConfig {
+
+ private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ @Bean
+ public FilterRegistrationBean jwtAuthenticationFilterBean() {
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(jwtAuthenticationFilter);
+ registrationBean.addUrlPatterns("/api/*");
+ registrationBean.setOrder(1); //先执行
+ return registrationBean;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/system/config/MybatisPlusConfig.java b/src/main/java/com/qyft/ms/system/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..3db265f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/config/MybatisPlusConfig.java
@@ -0,0 +1,61 @@
+package com.qyft.ms.system.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.qyft.ms.system.core.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;
+ }
+
+ /**
+ * 分页插件,自动识别数据库类型
+ * https://baomidou.com/guide/interceptor-pagination.html
+ */
+ public PaginationInnerInterceptor paginationInnerInterceptor() {
+ PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+ // 设置数据库类型
+ paginationInnerInterceptor.setDbType(DbType.SQLITE);
+ // 设置最大单页限制数量,默认 500 条,-1 不受限制
+ paginationInnerInterceptor.setMaxLimit(-1L);
+ return paginationInnerInterceptor;
+ }
+
+ /**
+ * 乐观锁插件
+ * https://baomidou.com/guide/interceptor-optimistic-locker.html
+ */
+ public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+ return new OptimisticLockerInnerInterceptor();
+ }
+
+ /**
+ * 自动填充数据库创建人、创建时间、更新人、更新时间
+ */
+ @Bean
+ public GlobalConfig globalConfig() {
+ GlobalConfig globalConfig = new GlobalConfig();
+ globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
+ return globalConfig;
+ }
+}
diff --git a/src/main/java/com/qyft/ms/system/config/SwaggerConfig.java b/src/main/java/com/qyft/ms/system/config/SwaggerConfig.java
new file mode 100644
index 0000000..c09cb0f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/config/SwaggerConfig.java
@@ -0,0 +1,69 @@
+package com.qyft.ms.system.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.SecurityRequirement;
+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("基质喷涂")
+ .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")
+ )
+ );
+ }
+
+
+}
diff --git a/src/main/java/com/qyft/ms/system/config/WebConfig.java b/src/main/java/com/qyft/ms/system/config/WebConfig.java
new file mode 100644
index 0000000..4fae8bd
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/config/WebConfig.java
@@ -0,0 +1,16 @@
+package com.qyft.ms.system.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("*");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/controller/AuthController.java b/src/main/java/com/qyft/ms/system/controller/AuthController.java
new file mode 100644
index 0000000..ecd464c
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/controller/AuthController.java
@@ -0,0 +1,46 @@
+package com.qyft.ms.system.controller;
+
+import com.qyft.ms.system.common.enums.DeletedEnum;
+import com.qyft.ms.system.common.result.Result;
+import com.qyft.ms.system.common.result.ResultCode;
+import com.qyft.ms.system.common.utils.JwtUtil;
+import com.qyft.ms.system.model.entity.User;
+import com.qyft.ms.system.model.form.LoginForm;
+import com.qyft.ms.system.service.IUserService;
+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.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Objects;
+
+/**
+ * 认证控制
+ */
+@Tag(name = "认证")
+@RestController
+@RequestMapping("/api/auth")
+@RequiredArgsConstructor
+@Slf4j
+public class AuthController {
+
+ private final IUserService userService;
+
+ @Operation(summary = "账号密码登录")
+ @PostMapping("/login")
+ public Result login(@RequestBody LoginForm loginForm) {
+ // 查找用户
+ User user = userService.findByUsername(loginForm.getUsername());
+ if (user != null && !Objects.equals(user.getIsDeleted(), DeletedEnum.ENABLE.getValue()) && user.getPassword().equals(loginForm.getPassword())) {
+ String token = JwtUtil.createJWE(loginForm.getUsername());
+ return Result.success("Bearer " + token);
+ }
+ return Result.failed(ResultCode.USER_PASSWORD_ERROR);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/controller/RoleController.java b/src/main/java/com/qyft/ms/system/controller/RoleController.java
new file mode 100644
index 0000000..3402bf7
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/controller/RoleController.java
@@ -0,0 +1,75 @@
+package com.qyft.ms.system.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qyft.ms.system.common.base.BasePageQuery;
+import com.qyft.ms.system.common.result.PageResult;
+import com.qyft.ms.system.common.result.Result;
+import com.qyft.ms.system.common.result.ResultCode;
+import com.qyft.ms.system.model.entity.Role;
+import com.qyft.ms.system.service.IRoleService;
+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.*;
+
+@Tag(name = "角色接口")
+@RestController
+@RequestMapping("/api/role")
+@RequiredArgsConstructor
+@Slf4j
+public class RoleController {
+
+ private final IRoleService roleService;
+
+ @Operation(summary = "角色列表")
+ @GetMapping("/list")
+ public PageResult getAllRole(BasePageQuery pageQuery) {
+ IPage result = roleService.page(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), null);
+ return PageResult.success(result);
+ }
+
+
+ @Operation(summary = "添加新角色")
+ @PostMapping("/")
+ public Result addRole(@RequestBody Role role) {
+ Role existingRole = roleService.findByCode(role.getCode());
+ if (existingRole == null) {
+ boolean isSuccess = roleService.addRole(role);
+ if (isSuccess) {
+ return Result.success();
+ }
+ } else {
+ return Result.failed(ResultCode.DATA_ALREADY_EXISTS);
+ }
+ return Result.failed();
+ }
+
+ @Operation(summary = "更新角色")
+ @PutMapping("/{id}")
+ public Result updateRole(@PathVariable Long id, @RequestBody Role role) {
+ role.setId(id);
+ Role existingRole = roleService.findByCode(role.getCode());
+ if (existingRole == null) {
+ boolean isSuccess = roleService.updateRole(role);
+ if (isSuccess) {
+ return Result.success();
+ }
+ } else {
+ return Result.failed(ResultCode.DATA_ALREADY_EXISTS);
+ }
+ return Result.failed();
+ }
+
+ @Operation(summary = "删除角色")
+ @DeleteMapping("/{id}")
+ public Result deleteRole(@PathVariable Long id) {
+ boolean isSuccess = roleService.deleteRole(id);
+ if (isSuccess) {
+ return Result.success();
+ }
+ return Result.failed();
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/system/controller/UserController.java b/src/main/java/com/qyft/ms/system/controller/UserController.java
new file mode 100644
index 0000000..3871884
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/controller/UserController.java
@@ -0,0 +1,78 @@
+package com.qyft.ms.system.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qyft.ms.system.common.base.BasePageQuery;
+import com.qyft.ms.system.common.result.PageResult;
+import com.qyft.ms.system.common.result.Result;
+import com.qyft.ms.system.common.result.ResultCode;
+import com.qyft.ms.system.model.entity.User;
+import com.qyft.ms.system.service.IUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+@Tag(name = "用户接口")
+@RestController
+@RequestMapping("/api/user")
+@RequiredArgsConstructor
+@Slf4j
+public class UserController {
+ private final IUserService userService;
+
+ @Operation(summary = "用户列表")
+ @GetMapping("/list")
+ public PageResult getAllUsers(BasePageQuery pageQuery) {
+ IPage result = userService.page(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), null);
+ return PageResult.success(result);
+ }
+
+ @Operation(summary = "添加新用户")
+ @PostMapping("/")
+ public Result addUser(@RequestBody User user) {
+ User existingUser = userService.findByUsername(user.getUsername());
+ if (existingUser == null) {
+ boolean isSuccess = userService.addUser(user);
+ if (isSuccess) {
+ return Result.success();
+ }
+ } else {
+ return Result.failed(ResultCode.USERNAME_ALREADY_EXISTS);
+ }
+ return Result.failed();
+ }
+
+ @Operation(summary = "当前用户信息")
+ @GetMapping("/current")
+ public Result currentUser() {
+ User user = userService.currentUser();
+ if (user == null) {
+ return Result.failed(ResultCode.ACCESS_TOKEN_INVALID);
+ }
+ return Result.success(user);
+ }
+
+ @Operation(summary = "更新用户信息")
+ @PutMapping("/{id}")
+ public Result updateUser(@PathVariable Long id, @RequestBody User user) {
+ user.setId(id);
+ boolean isSuccess = userService.updateUser(user);
+ if (isSuccess) {
+ return Result.success();
+ }
+ return Result.failed();
+ }
+
+ @Operation(summary = "删除用户")
+ @DeleteMapping("/{ids}")
+ public Result deleteUser(@Parameter(description = "用户ID,多个以英文逗号(,)分割") @PathVariable String ids) {
+ boolean isSuccess = userService.deleteUser(ids);
+ if (isSuccess) {
+ return Result.success();
+ }
+ return Result.failed();
+ }
+}
diff --git a/src/main/java/com/qyft/ms/system/core/MyMetaObjectHandler.java b/src/main/java/com/qyft/ms/system/core/MyMetaObjectHandler.java
new file mode 100644
index 0000000..f5523ac
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/core/MyMetaObjectHandler.java
@@ -0,0 +1,36 @@
+package com.qyft.ms.system.core;
+
+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);
+ }
+
+}
diff --git a/src/main/java/com/qyft/ms/system/filter/JwtAuthenticationFilter.java b/src/main/java/com/qyft/ms/system/filter/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..3cec2ac
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/filter/JwtAuthenticationFilter.java
@@ -0,0 +1,52 @@
+package com.qyft.ms.system.filter;
+
+import com.qyft.ms.system.common.result.ResultCode;
+import com.qyft.ms.system.common.utils.JwtUtil;
+import com.qyft.ms.system.common.utils.ResponseUtils;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ @Value("${jwt.enabled:true}") // 从配置文件中读取 jwt.enabled,默认为 true
+ private boolean jwtEnabled;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ String token = getTokenFromRequest(request);
+ if (shouldBypass(request)) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+ if (token != null && JwtUtil.parseJWE(token) != null) {
+ request.setAttribute("token", token); // 可以将 token 存储在 request 属性中,供后续使用
+ } else {
+ if (jwtEnabled) {
+ ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
+ return;
+ }
+ }
+ filterChain.doFilter(request, response);
+ }
+
+ private boolean shouldBypass(HttpServletRequest request) {
+ String uri = request.getRequestURI();
+ return uri.startsWith("/api/auth/login");
+ }
+
+ private String getTokenFromRequest(HttpServletRequest request) {
+ String header = request.getHeader("Authorization");
+ if (header != null && header.startsWith("Bearer ")) {
+ return header.substring(7); // 截取 "Bearer " 后的 token 部分
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/mapper/RoleMapper.java b/src/main/java/com/qyft/ms/system/mapper/RoleMapper.java
new file mode 100644
index 0000000..0952157
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/mapper/RoleMapper.java
@@ -0,0 +1,12 @@
+package com.qyft.ms.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qyft.ms.system.model.entity.Role;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface RoleMapper extends BaseMapper {
+
+ Role findByCode(String code);
+
+}
diff --git a/src/main/java/com/qyft/ms/system/mapper/UserMapper.java b/src/main/java/com/qyft/ms/system/mapper/UserMapper.java
new file mode 100644
index 0000000..1370763
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/mapper/UserMapper.java
@@ -0,0 +1,17 @@
+package com.qyft.ms.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qyft.ms.system.model.entity.User;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 用户持久层接口
+ */
+@Mapper
+public interface UserMapper extends BaseMapper {
+
+
+ User findByUsername(String username);
+
+
+}
diff --git a/src/main/java/com/qyft/ms/system/model/entity/Role.java b/src/main/java/com/qyft/ms/system/model/entity/Role.java
new file mode 100644
index 0000000..032a5cc
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/model/entity/Role.java
@@ -0,0 +1,26 @@
+package com.qyft.ms.system.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 角色实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "角色信息")
+@TableName("sys_role")
+@Data
+public class Role extends BaseEntity {
+ @NotBlank()
+ @Schema(description = "角色名称")
+ private String name;
+
+ @NotBlank()
+ @Schema(description = "角色编码")
+ private String code;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/model/entity/User.java b/src/main/java/com/qyft/ms/system/model/entity/User.java
new file mode 100644
index 0000000..1533137
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/model/entity/User.java
@@ -0,0 +1,39 @@
+package com.qyft.ms.system.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qyft.ms.system.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "用户基础信息")
+@TableName("sys_user")
+@Data
+public class User extends BaseEntity {
+
+ @NotBlank()
+ @Schema(description = "用户名")
+ private String username;
+
+ @NotBlank()
+ @Schema(description = "昵称")
+ private String nickname;
+
+ @NotBlank()
+ @Schema(description = "密码")
+ private String password;
+
+ @Schema(description = "人员角色")
+ private Long roleId;
+
+ @Schema(description = "是否删除(0-否 1-是)")
+ @TableLogic(value = "0", delval = "1")
+ private Integer isDeleted;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/qyft/ms/system/model/form/LoginForm.java b/src/main/java/com/qyft/ms/system/model/form/LoginForm.java
new file mode 100644
index 0000000..3fa87b1
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/model/form/LoginForm.java
@@ -0,0 +1,22 @@
+package com.qyft.ms.system.model.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+/**
+ * 用户登录表单
+ */
+@Schema(description = "用户登录")
+@Data
+public class LoginForm {
+
+ @NotBlank()
+ @Schema(description = "用户名", example = "admin")
+ private String username;
+
+ @NotBlank()
+ @Schema(description = "用户密码", example = "12345")
+ private String password;
+
+}
diff --git a/src/main/java/com/qyft/ms/system/service/IRoleService.java b/src/main/java/com/qyft/ms/system/service/IRoleService.java
new file mode 100644
index 0000000..7e2f58a
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/service/IRoleService.java
@@ -0,0 +1,20 @@
+package com.qyft.ms.system.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qyft.ms.system.model.entity.Role;
+
+/**
+ * 角色业务接口层
+ */
+public interface IRoleService extends IService {
+
+ Role findByCode(String code);
+
+ boolean addRole(Role role);
+
+ boolean updateRole(Role role);
+
+ boolean deleteRole(Long roleId);
+
+}
diff --git a/src/main/java/com/qyft/ms/system/service/IUserService.java b/src/main/java/com/qyft/ms/system/service/IUserService.java
new file mode 100644
index 0000000..7918d87
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/service/IUserService.java
@@ -0,0 +1,21 @@
+package com.qyft.ms.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qyft.ms.system.model.entity.User;
+
+/**
+ * 用户业务接口
+ */
+public interface IUserService extends IService {
+
+ User findByUsername(String username);
+
+ boolean addUser(User user);
+
+ boolean updateUser(User user);
+
+ boolean deleteUser(String idsStr);
+
+ User currentUser();
+
+}
diff --git a/src/main/java/com/qyft/ms/system/service/impl/IRoleServiceImpl.java b/src/main/java/com/qyft/ms/system/service/impl/IRoleServiceImpl.java
new file mode 100644
index 0000000..d16249f
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/service/impl/IRoleServiceImpl.java
@@ -0,0 +1,38 @@
+package com.qyft.ms.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qyft.ms.system.mapper.RoleMapper;
+import com.qyft.ms.system.model.entity.Role;
+import com.qyft.ms.system.service.IRoleService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 角色业务实现类
+ */
+@Service
+@RequiredArgsConstructor
+public class IRoleServiceImpl extends ServiceImpl implements IRoleService {
+
+ @Override
+ public Role findByCode(String code) {
+ return this.baseMapper.findByCode(code);
+ }
+
+ @Override
+ public boolean addRole(Role role) {
+ return this.baseMapper.insert(role) > 0;
+ }
+
+ @Override
+ public boolean updateRole(Role role) {
+ return this.baseMapper.updateById(role) > 0;
+ }
+
+ @Override
+ public boolean deleteRole(Long roleId) {
+ return this.baseMapper.deleteById(roleId) > 0;
+ }
+
+
+}
diff --git a/src/main/java/com/qyft/ms/system/service/impl/IUserServiceImpl.java b/src/main/java/com/qyft/ms/system/service/impl/IUserServiceImpl.java
new file mode 100644
index 0000000..d5cc7a8
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/service/impl/IUserServiceImpl.java
@@ -0,0 +1,69 @@
+package com.qyft.ms.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qyft.ms.system.common.utils.JwtUtil;
+import com.qyft.ms.system.mapper.UserMapper;
+import com.qyft.ms.system.model.entity.User;
+import com.qyft.ms.system.service.IUserService;
+import io.jsonwebtoken.Claims;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户业务实现类
+ */
+@Service
+@RequiredArgsConstructor
+public class IUserServiceImpl extends ServiceImpl implements IUserService {
+
+ private final HttpServletRequest request;
+
+ @Override
+ public User findByUsername(String username) {
+ return this.baseMapper.findByUsername(username);
+ }
+
+ @Override
+ public boolean addUser(User user) {
+ return this.baseMapper.insert(user) > 0;
+ }
+
+ @Override
+ public boolean updateUser(User user) {
+ return this.baseMapper.updateById(user) > 0;
+ }
+
+ @Override
+ public boolean deleteUser(String idsStr) {
+ List ids = Arrays.stream(idsStr.split(","))
+ .map(Long::parseLong)
+ .collect(Collectors.toList());
+ return this.removeByIds(ids);
+ }
+
+ /**
+ * 获取当前登录用户
+ */
+ @Override
+ public User currentUser() {
+ try {
+ String token = (String) request.getAttribute("token");
+ if (token == null || token.isEmpty()) {
+ return null;
+ }
+ Claims claims = JwtUtil.parseJWE(token);
+ String username = claims.getSubject();
+ User user = findByUsername(username);
+ return user;
+ } catch (Exception e) {
+ log.error("获取当前登录用户错误", e);
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..3d19d90
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,43 @@
+server:
+ servlet:
+ context-path: /
+ port: 8090
+
+spring:
+ application:
+ name: matrix-spray
+ datasource:
+ url: jdbc:sqlite:matrix-spray.db
+ driver-class-name: org.sqlite.JDBC
+# sql:
+# init:
+# mode:
+
+mybatis-plus:
+ configuration:
+ # 开启 SQL 日志输出(可选)
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+ # 如果需要加载 XML 文件(自定义 SQL),可配置 mapper-locations:
+ mapper-locations: classpath*:mapper/*.xml
+
+# 可选:开启 SQL 打印,调试时方便查看
+logging:
+ level:
+ root: INFO
+ org.mybatis: DEBUG
+
+springdoc:
+ default-flat-param-object: true
+
+jwt:
+ enabled: false # 是否启用权限认证,设置为 true 启用,false 禁用
+
+#与设备TCP链接
+tcp:
+ enable: false # 是否开启 TCP 连接
+ host: 127.0.0.1
+ port: 9080
+ reconnect: 5000 # 断线重连间隔(单位:毫秒)
+ timeout: 10000 # 连接超时时间(单位:毫秒)
+ feedback-timeout: 500000
+
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
new file mode 100644
index 0000000..30d4f25
--- /dev/null
+++ b/src/main/resources/logback.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+ ${log.pattern}
+
+
+
+
+
+ ${log.path}/sys-info.log
+
+
+
+ ${log.path}/sys-info.%d{yyyy-MM-dd}.log
+
+ 60
+
+
+ ${log.pattern}
+
+
+
+ INFO
+
+ ACCEPT
+
+ DENY
+
+
+
+
+ ${log.path}/sys-error.log
+
+
+
+ ${log.path}/sys-error.%d{yyyy-MM-dd}.log
+
+ 60
+
+
+ ${log.pattern}
+
+
+
+ ERROR
+
+ ACCEPT
+
+ DENY
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/mapper/SysSettingsMapper.xml b/src/main/resources/mapper/SysSettingsMapper.xml
new file mode 100644
index 0000000..d5effbb
--- /dev/null
+++ b/src/main/resources/mapper/SysSettingsMapper.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+ UPDATE sys_settings
+ SET
+ value1 = CASE WHEN #{value} IS NOT NULL THEN #{value} ELSE value1 END,
+ value3 = CASE WHEN #{temperature} IS NOT NULL THEN #{temperature} ELSE value3 END,
+ value4 = CASE WHEN #{craftId} IS NOT NULL THEN #{craftId} ELSE value4 END
+ WHERE id = #{id}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/mapper/device/CtrlFuncMapper.xml b/src/main/resources/mapper/device/CtrlFuncMapper.xml
new file mode 100644
index 0000000..36aa47c
--- /dev/null
+++ b/src/main/resources/mapper/device/CtrlFuncMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/main/resources/mapper/device/CtrlFuncStepMapper.xml b/src/main/resources/mapper/device/CtrlFuncStepMapper.xml
new file mode 100644
index 0000000..8431eb4
--- /dev/null
+++ b/src/main/resources/mapper/device/CtrlFuncStepMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/main/resources/mapper/system/RoleMapper.xml b/src/main/resources/mapper/system/RoleMapper.xml
new file mode 100644
index 0000000..106dc76
--- /dev/null
+++ b/src/main/resources/mapper/system/RoleMapper.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/system/UserMapper.xml b/src/main/resources/mapper/system/UserMapper.xml
new file mode 100644
index 0000000..947de6e
--- /dev/null
+++ b/src/main/resources/mapper/system/UserMapper.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/test/java/com/qyft/ms/GraphiteDigestionInstrumentApplicationTests.java b/src/test/java/com/qyft/ms/GraphiteDigestionInstrumentApplicationTests.java
new file mode 100644
index 0000000..74fb606
--- /dev/null
+++ b/src/test/java/com/qyft/ms/GraphiteDigestionInstrumentApplicationTests.java
@@ -0,0 +1,13 @@
+package com.qyft.ms;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class GraphiteDigestionInstrumentApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}