commit 75a97c888450ec001eaa60a26d8e5de82d3d2766
Author: 白凤吉
Date: Sun Mar 16 11:11:58 2025 +0800
项目初始化
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..6a6db4c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,68 @@
+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'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3'
+ //++++++++项目级别的放到下面++++++++
+
+ //++++++++项目级别的放到上面++++++++
+
+ 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'
+}
+
+// 使用JUnit平台运行器
+tasks.withType(Test).configureEach {
+ useJUnitPlatform()
+
+ // 生成详细测试报告
+ reports {
+ junitXml.required = true // 生成XML格式报告(兼容工具解析)
+ html.required = true // 生成HTML报告
+ }
+
+ // 解决控制台日志中文乱码问题(Windows下常见)
+ systemProperty 'file.encoding', 'UTF-8'
+}
\ 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/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/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/system/common/base/BaseEntity.java b/src/main/java/com/qyft/ms/system/common/base/BaseEntity.java
new file mode 100644
index 0000000..d85d64a
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/common/base/BaseEntity.java
@@ -0,0 +1,44 @@
+package com.qyft.ms.system.common.base;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 基础实体类
+ * 实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等
+ */
+@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..75c6709
--- /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("/**")
+ .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..8cc10c1
--- /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.UserService;
+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 UserService 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..09b333f
--- /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.RoleService;
+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 RoleService 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..f683766
--- /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.UserService;
+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 UserService 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/generator/CodeGenerator.java b/src/main/java/com/qyft/ms/system/generator/CodeGenerator.java
new file mode 100644
index 0000000..923c5f6
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/generator/CodeGenerator.java
@@ -0,0 +1,28 @@
+package com.qyft.ms.system.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/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/RoleService.java b/src/main/java/com/qyft/ms/system/service/RoleService.java
new file mode 100644
index 0000000..bc907cc
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/service/RoleService.java
@@ -0,0 +1,33 @@
+package com.qyft.ms.system.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qyft.ms.system.mapper.RoleMapper;
+import com.qyft.ms.system.model.entity.Role;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 角色业务实现类
+ */
+@Service
+@RequiredArgsConstructor
+public class RoleService extends ServiceImpl{
+
+ public Role findByCode(String code) {
+ return this.baseMapper.findByCode(code);
+ }
+
+ public boolean addRole(Role role) {
+ return this.baseMapper.insert(role) > 0;
+ }
+
+ public boolean updateRole(Role role) {
+ return this.baseMapper.updateById(role) > 0;
+ }
+
+ public boolean deleteRole(Long roleId) {
+ return this.baseMapper.deleteById(roleId) > 0;
+ }
+
+
+}
diff --git a/src/main/java/com/qyft/ms/system/service/UserService.java b/src/main/java/com/qyft/ms/system/service/UserService.java
new file mode 100644
index 0000000..ee65790
--- /dev/null
+++ b/src/main/java/com/qyft/ms/system/service/UserService.java
@@ -0,0 +1,63 @@
+package com.qyft.ms.system.service;
+
+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 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 UserService extends ServiceImpl{
+
+ private final HttpServletRequest request;
+
+ public User findByUsername(String username) {
+ return this.baseMapper.findByUsername(username);
+ }
+
+ public boolean addUser(User user) {
+ return this.baseMapper.insert(user) > 0;
+ }
+
+ public boolean updateUser(User user) {
+ return this.baseMapper.updateById(user) > 0;
+ }
+
+ public boolean deleteUser(String idsStr) {
+ List ids = Arrays.stream(idsStr.split(","))
+ .map(Long::parseLong)
+ .collect(Collectors.toList());
+ return this.removeByIds(ids);
+ }
+
+ /**
+ * 获取当前登录用户
+ */
+ public User currentUser() {
+ try {
+ String token = (String) request.getAttribute("token");
+ if (token == null || token.isEmpty()) {
+ return null;
+ }
+ Claims claims = JwtUtil.parseJWE(token);
+ assert claims != null;
+ String username = claims.getSubject();
+ return findByUsername(username);
+ } 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..b270f02
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,48 @@
+server:
+ servlet:
+ context-path: /
+ port: 8080
+
+spring:
+ application:
+ name: matrix-spray
+ datasource:
+ url: jdbc:sqlite:../db/matrix-spray.db
+ driver-class-name: org.sqlite.JDBC
+ sql:
+ init:
+ # always embedded never
+ mode: always
+ schema-locations: classpath:/sql/init.sql
+
+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 连接
+ server-enable: true # 是否开启 TCP 连接
+ host: 127.0.0.1
+# host: 192.168.1.168
+# host: 192.168.1.168
+ 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/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/main/resources/sql/init.sql b/src/main/resources/sql/init.sql
new file mode 100644
index 0000000..c0f18cc
--- /dev/null
+++ b/src/main/resources/sql/init.sql
@@ -0,0 +1,102 @@
+-- 创建 sys_user 表,用于存储用户信息
+CREATE TABLE IF NOT EXISTS sys_user
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT, -- 用户ID,自增长主键
+ username TEXT NOT NULL, -- 用户名
+ nickname TEXT, -- 用户昵称
+ password TEXT NOT NULL, -- 密码
+ role_id INTEGER, -- 角色ID,关联sys_role表
+ is_deleted TINYINT DEFAULT 0, -- 删除标记(0:未删除,1:已删除)
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 更新时间
+);
+
+-- 插入测试数据到 sys_user 表
+INSERT INTO sys_user (username, nickname, password, role_id, is_deleted)
+VALUES ('admin', 'Admin', '12345', 1, 0),
+ ('test', 'test', 'test123', 3, 0);
+
+
+-- 创建 sys_role 表,用于存储角色信息
+CREATE TABLE IF NOT EXISTS sys_role
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT, -- 角色ID,自增长主键
+ name TEXT NOT NULL, -- 角色名称
+ code TEXT NOT NULL, -- 角色代码,如 ADMIN, USER等
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 更新时间
+);
+
+-- 插入角色数据到 sys_role 表
+INSERT INTO sys_role (name, code)
+VALUES ('管理员', 'ADMIN'),
+ ('普通用户', 'USER'),
+ ('测试用户', 'TEST');
+
+
+-- 创建 matrix 表,用于存储基质类型信息
+CREATE TABLE IF NOT EXISTS matrix
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT, -- 基质类型ID,自增长主键
+ 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, -- 工艺ID,自增长主键
+ name TEXT NOT NULL, -- 工艺名称
+ matrix_id INTEGER NOT NULL, -- 关联的基质类型ID(外键)
+ matrix_path_type TEXT NOT NULL, -- 基质路径类型
+ motor_z_height INTEGER, -- 电机Z轴高度
+ gas_pressure INTEGER, -- 气压
+ volume INTEGER, -- 容积
+ matrix_flow_velocity INTEGER, -- 基质流速
+ high_voltage BOOLEAN, -- 是否采用高压(TRUE 或 FALSE)
+ high_voltage_value INTEGER, -- 高压值
+ spacing INTEGER, -- 间距
+ moving_speed INTEGER, -- 移动速度
+ times INTEGER, -- 次数
+ create_user INTEGER, -- 创建用户ID
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 更新时间
+);
+
+
+-- 创建 operation_log 表,用于存储操作记录信息
+CREATE TABLE IF NOT EXISTS operation_log
+(
+ id INTEGER PRIMARY KEY AUTOINCREMENT, -- 日志记录ID,自增长主键
+ matrix_id INTEGER, -- 关联的基质ID
+ matrix_info TEXT, -- 基质信息详情
+ status INTEGER, -- 状态标识(如操作结果状态码)
+ create_user INTEGER, -- 创建该日志的用户ID
+ 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, -- 配置ID,自增长主键
+ parent_id INTEGER, -- 父级配置ID(用于层级配置)
+ name TEXT NOT NULL, -- 配置名称
+ code TEXT, -- 配置代码
+ value TEXT -- 配置值
+);
+
+
+-- 创建 position 表,用于存储设备固定点位信息
+CREATE TABLE IF NOT EXISTS position (
+ id INTEGER PRIMARY KEY AUTOINCREMENT, -- 点位ID,自增长主键
+ point_name TEXT NOT NULL, -- 点位名称
+ x REAL NOT NULL, -- X坐标
+ y REAL NOT NULL, -- Y坐标
+ z REAL NOT NULL, -- Z坐标
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 更新时间
+);