alibct 2 vuotta sitten
commit
62e169cc0d
100 muutettua tiedostoa jossa 7096 lisäystä ja 0 poistoa
  1. 32 0
      .gitignore
  2. BIN
      .mvn/wrapper/maven-wrapper.jar
  3. 18 0
      .mvn/wrapper/maven-wrapper.properties
  4. 0 0
      README.md
  5. 316 0
      mvnw
  6. 188 0
      mvnw.cmd
  7. 136 0
      pom.xml
  8. 223 0
      sql/init.sql
  9. 21 0
      src/main/java/com/pavis/backend/slim/BackendSlimApplication.java
  10. 76 0
      src/main/java/com/pavis/backend/slim/common/constant/Constant.java
  11. 94 0
      src/main/java/com/pavis/backend/slim/common/constant/HttpStatus.java
  12. 49 0
      src/main/java/com/pavis/backend/slim/common/exception/GlobalException.java
  13. 63 0
      src/main/java/com/pavis/backend/slim/common/exception/ServiceException.java
  14. 22 0
      src/main/java/com/pavis/backend/slim/common/exception/UtilException.java
  15. 84 0
      src/main/java/com/pavis/backend/slim/common/exception/base/BaseException.java
  16. 17 0
      src/main/java/com/pavis/backend/slim/common/exception/file/FileException.java
  17. 52 0
      src/main/java/com/pavis/backend/slim/common/exception/file/FileUploadException.java
  18. 29 0
      src/main/java/com/pavis/backend/slim/common/exception/job/TaskException.java
  19. 16 0
      src/main/java/com/pavis/backend/slim/common/exception/user/UserException.java
  20. 14 0
      src/main/java/com/pavis/backend/slim/common/exception/user/UserNotExistsException.java
  21. 14 0
      src/main/java/com/pavis/backend/slim/common/exception/user/UserPasswordNotMatchException.java
  22. 14 0
      src/main/java/com/pavis/backend/slim/common/exception/user/UserPasswordRetryLimitExceedException.java
  23. 133 0
      src/main/java/com/pavis/backend/slim/common/utils/FileUtils.java
  24. 25 0
      src/main/java/com/pavis/backend/slim/common/utils/MessageUtils.java
  25. 79 0
      src/main/java/com/pavis/backend/slim/common/utils/SecurityUtils.java
  26. 172 0
      src/main/java/com/pavis/backend/slim/common/utils/ServletUtils.java
  27. 142 0
      src/main/java/com/pavis/backend/slim/common/utils/SpringUtils.java
  28. 26 0
      src/main/java/com/pavis/backend/slim/common/xss/Xss.java
  29. 31 0
      src/main/java/com/pavis/backend/slim/common/xss/XssValidator.java
  30. 14 0
      src/main/java/com/pavis/backend/slim/framework/annotation/Anonymous.java
  31. 14 0
      src/main/java/com/pavis/backend/slim/framework/config/BackendSlimConfig.java
  32. 28 0
      src/main/java/com/pavis/backend/slim/framework/config/MinioConfig.java
  33. 26 0
      src/main/java/com/pavis/backend/slim/framework/config/MyBatisPlusConfig.java
  34. 53 0
      src/main/java/com/pavis/backend/slim/framework/config/ResourcesConfig.java
  35. 141 0
      src/main/java/com/pavis/backend/slim/framework/config/SecurityConfig.java
  36. 65 0
      src/main/java/com/pavis/backend/slim/framework/config/properties/PermitAllUrlProperties.java
  37. 45 0
      src/main/java/com/pavis/backend/slim/framework/interceptor/RepeatSubmitInterceptor.java
  38. 26 0
      src/main/java/com/pavis/backend/slim/framework/interceptor/annotation/RepeatSubmit.java
  39. 97 0
      src/main/java/com/pavis/backend/slim/framework/interceptor/impl/SameUrlDataInterceptor.java
  40. 179 0
      src/main/java/com/pavis/backend/slim/framework/minio/MinioStorage.java
  41. 243 0
      src/main/java/com/pavis/backend/slim/framework/redis/RedisCache.java
  42. 38 0
      src/main/java/com/pavis/backend/slim/framework/security/LoginBody.java
  43. 168 0
      src/main/java/com/pavis/backend/slim/framework/security/LoginUser.java
  44. 24 0
      src/main/java/com/pavis/backend/slim/framework/security/context/AuthenticationContextHolder.java
  45. 28 0
      src/main/java/com/pavis/backend/slim/framework/security/context/PermissionContextHolder.java
  46. 41 0
      src/main/java/com/pavis/backend/slim/framework/security/filter/JwtAuthenticationTokenFilter.java
  47. 31 0
      src/main/java/com/pavis/backend/slim/framework/security/handle/AuthenticationEntryPointImpl.java
  48. 39 0
      src/main/java/com/pavis/backend/slim/framework/security/handle/LogoutSuccessHandlerImpl.java
  49. 106 0
      src/main/java/com/pavis/backend/slim/framework/security/service/LoginService.java
  50. 73 0
      src/main/java/com/pavis/backend/slim/framework/security/service/PasswordService.java
  51. 151 0
      src/main/java/com/pavis/backend/slim/framework/security/service/PermissionService.java
  52. 70 0
      src/main/java/com/pavis/backend/slim/framework/security/service/SysPermissionService.java
  53. 191 0
      src/main/java/com/pavis/backend/slim/framework/security/service/TokenService.java
  54. 52 0
      src/main/java/com/pavis/backend/slim/framework/security/service/UserDetailsServiceImpl.java
  55. 194 0
      src/main/java/com/pavis/backend/slim/framework/web/domain/AjaxResult.java
  56. 121 0
      src/main/java/com/pavis/backend/slim/framework/web/domain/BaseEntity.java
  57. 96 0
      src/main/java/com/pavis/backend/slim/framework/web/exception/GlobalExceptionHandler.java
  58. 65 0
      src/main/java/com/pavis/backend/slim/project/system/controller/SysFileController.java
  59. 112 0
      src/main/java/com/pavis/backend/slim/project/system/controller/SysKbController.java
  60. 17 0
      src/main/java/com/pavis/backend/slim/project/system/controller/SysKgController.java
  61. 67 0
      src/main/java/com/pavis/backend/slim/project/system/controller/SysLoginController.java
  62. 118 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysEntity.java
  63. 100 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysEntityInstance.java
  64. 192 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysFile.java
  65. 118 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysKb.java
  66. 42 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysKbFile.java
  67. 104 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysKg.java
  68. 261 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysMenu.java
  69. 149 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysRole.java
  70. 249 0
      src/main/java/com/pavis/backend/slim/project/system/domain/SysUser.java
  71. 91 0
      src/main/java/com/pavis/backend/slim/project/system/domain/vo/TreeFile.java
  72. 12 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysEntityInstanceMapper.java
  73. 13 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysEntityMapper.java
  74. 25 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysFileMapper.java
  75. 14 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysKbFileMapper.java
  76. 13 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysKbMapper.java
  77. 13 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysKgMapper.java
  78. 13 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysMenuMapper.java
  79. 13 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysRoleMapper.java
  80. 29 0
      src/main/java/com/pavis/backend/slim/project/system/mapper/SysUserMapper.java
  81. 73 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysFileService.java
  82. 33 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysKbFileService.java
  83. 46 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysKbService.java
  84. 19 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysKgService.java
  85. 37 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysMenuService.java
  86. 20 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysRoleService.java
  87. 19 0
      src/main/java/com/pavis/backend/slim/project/system/service/SysUserService.java
  88. 217 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysFileServiceImpl.java
  89. 59 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysKbFileServiceImpl.java
  90. 50 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysKbServiceImpl.java
  91. 21 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysKgServiceImpl.java
  92. 33 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysMenuServiceImpl.java
  93. 21 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysRoleServiceImpl.java
  94. 19 0
      src/main/java/com/pavis/backend/slim/project/system/service/impl/SysUserServiceImpl.java
  95. 41 0
      src/main/resources/application-dev.yml
  96. 1 0
      src/main/resources/application-local.yml
  97. 1 0
      src/main/resources/application-prod.yml
  98. 43 0
      src/main/resources/application.yml
  99. 38 0
      src/main/resources/i18n/messages.properties
  100. 35 0
      src/main/resources/mapper/system/SysFileMapper.xml

+ 32 - 0
.gitignore

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

BIN
.mvn/wrapper/maven-wrapper.jar


+ 18 - 0
.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   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.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

+ 0 - 0
README.md


+ 316 - 0
mvnw

@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  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
+  else
+    JAVACMD="`\\unset -f command; \\command -v java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    fi
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+    if $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 188 - 0
mvnw.cmd

@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+    IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+  %JVM_CONFIG_MAVEN_PROPS% ^
+  %MAVEN_OPTS% ^
+  %MAVEN_DEBUG_OPTS% ^
+  -classpath %WRAPPER_JAR% ^
+  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%

+ 136 - 0
pom.xml

@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.5.14</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.pavis</groupId>
+    <artifactId>backend-slim</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>backend-slim</name>
+    <description>backend-slim</description>
+    <properties>
+        <java.version>1.8</java.version>
+        <jwt.version>0.9.1</jwt.version>
+        <minio.version>8.5.2</minio.version>
+        <mysql.version>8.0.29</mysql.version>
+        <okhttp.version>4.9.3</okhttp.version>
+        <hutool.version>5.8.16</hutool.version>
+        <knife4j.version>4.1.0</knife4j.version>
+        <fastjson.version>2.0.28</fastjson.version>
+        <mybatisplus.version>3.5.3</mybatisplus.version>
+        <mybatisplus.dynamic.version>3.6.1</mybatisplus.dynamic.version>
+    </properties>
+    <dependencies>
+        <!-- api文档 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
+            <version>${knife4j.version}</version>
+        </dependency>
+        <!-- MyBatisPlus多数据源配置 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>${mybatisplus.dynamic.version}</version>
+        </dependency>
+        <!-- MyBatis增强工具 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatisplus.version}</version>
+        </dependency>
+        <!-- Java工具类库 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+        <!-- JSON操作工具类 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+        <!-- Token生成与解析-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
+        <!-- minio依赖okhttp -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>${okhttp.version}</version>
+        </dependency>
+        <!-- 分布式文件对象存储 -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>${minio.version}</version>
+        </dependency>
+        <!-- redis 缓存操作 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <!-- 自定义验证注解 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- spring security 安全认证 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <!-- 自定义配置解析 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- 常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 223 - 0
sql/init.sql

@@ -0,0 +1,223 @@
+-- ----------------------------
+-- 1、用户信息表
+-- ----------------------------
+drop table if exists sys_user;
+create table sys_user
+(
+    user_id         bigint(20)  not null auto_increment comment '用户ID',
+    user_name       varchar(32) not null comment '用户账号',
+    nick_name       varchar(32) not null comment '用户昵称',
+    email           varchar(64)  default '' comment '用户邮箱',
+    telephone       varchar(16)  default '' comment '手机号码',
+    sex             char(1)      default '2' comment '用户性别(1男 0女 2未知)',
+    avatar          varchar(125) default '' comment '头像地址',
+    password        varchar(256) default '' comment '密码',
+    status          char(1)      default '1' comment '帐号状态(1正常 0停用)',
+    del_flag        char(1)      default '0' comment '删除标志(1已删除 0未删除)',
+    last_login_ip   varchar(128) default '' comment '最后登录IP',
+    last_login_date datetime comment '最后登录时间',
+    create_by       varchar(64)  default '' comment '创建者',
+    create_time     datetime comment '创建时间',
+    update_by       varchar(64)  default '' comment '更新者',
+    update_time     datetime comment '更新时间',
+    remark          varchar(512) default null comment '备注',
+    primary key (user_id)
+) engine = innodb
+  auto_increment = 100 comment = '用户信息表';
+
+-- ----------------------------
+-- 2、角色信息表
+-- ----------------------------
+drop table if exists sys_role;
+create table sys_role
+(
+    role_id     bigint(20)   not null auto_increment comment '角色ID',
+    role_name   varchar(32)  not null comment '角色名称',
+    role_key    varchar(128) not null comment '角色权限字符串',
+    role_sort   int(4)       not null comment '显示顺序',
+    status      char(1)      not null comment '角色状态(1正常 0停用)',
+    del_flag    char(1)      default '0' comment '删除标志(0存在 1删除)',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default null comment '备注',
+    primary key (role_id)
+) engine = innodb
+  auto_increment = 100 comment = '角色信息表';
+
+-- ----------------------------
+-- 初始化-角色信息表数据
+-- ----------------------------
+insert into sys_role
+values ('1', '管理员', 'admin', 1, 1, 0, 'admin', sysdate(), '', null, '管理员');
+insert into sys_role
+values ('2', '普通用户', 'user', 2, 1, 0, 'admin', sysdate(), '', null, '普通用户');
+
+-- ----------------------------
+-- 3、菜单权限表
+-- ----------------------------
+drop table if exists sys_menu;
+create table sys_menu
+(
+    menu_id     bigint(20)  not null auto_increment comment '菜单ID',
+    menu_name   varchar(64) not null comment '菜单名称',
+    parent_id   bigint(20)   default 0 comment '父菜单ID',
+    parent_name varchar(64) not null comment '父菜单名称',
+    order_num   int(4)       default 0 comment '显示顺序',
+    path        varchar(256) default '' comment '路由地址',
+    component   varchar(256) default null comment '组件路径',
+    query       varchar(256) default null comment '路由参数',
+    is_frame    int(1)       default 1 comment '是否为外链(1是 0否)',
+    is_cache    int(1)       default 0 comment '是否缓存(1缓存 0不缓存)',
+    menu_type   char(1)      default '' comment '菜单类型(F目录 M菜单 B按钮)',
+    visible     char(1)      default 0 comment '菜单状态(1显示 0隐藏)',
+    status      char(1)      default 0 comment '菜单状态(1正常 0停用)',
+    perms       varchar(128) default null comment '权限标识',
+    icon        varchar(128) default '#' comment '菜单图标',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default '' comment '备注',
+    primary key (menu_id)
+) engine = innodb
+  auto_increment = 2000 comment = '菜单权限表';
+
+-- ----------------------------
+-- 4、用户和角色关联表  用户N-1角色
+-- ----------------------------
+drop table if exists sys_user_role;
+create table sys_user_role
+(
+    user_id bigint(20) not null comment '用户ID',
+    role_id bigint(20) not null comment '角色ID',
+    primary key (user_id, role_id)
+) engine = innodb comment = '用户和角色关联表';
+
+-- ----------------------------
+-- 5、文件表
+-- ----------------------------
+drop table if exists sys_file;
+create table sys_file
+(
+    file_id       varchar(32) not null comment '文件ID',
+    user_id       bigint(20)  not null comment '用户ID',
+    name          varchar(64) comment '文件名称',
+    original_name varchar(256) comment '文件原名',
+    object_key    varchar(256) comment '文件在oss中的key值',
+    url           varchar(256) comment '文件url地址',
+    path          varchar(256) comment '文件路径',
+    size          bigint(20) comment '文件大小',
+    type          int comment '文件类型 0其他 1文档 2图片 3音频 4视频 -1文件夹',
+    suffix        varchar(8) comment '文件后缀',
+    icon          varchar(96) comment '图标',
+    identifier    varchar(128) comment '文件md5值',
+    is_dir        int          default 0 comment '是否是文件夹 1是 0不是',
+    create_by     varchar(64)  default '' comment '创建者',
+    create_time   datetime comment '创建时间',
+    update_by     varchar(64)  default '' comment '更新者',
+    update_time   datetime comment '更新时间',
+    remark        varchar(512) default '' comment '备注',
+    primary key (file_id)
+) engine = innodb comment = '文件信息表';
+
+
+-- ----------------------------
+-- 6、知识库表
+-- ----------------------------
+drop table if exists sys_kb;
+create table sys_kb
+(
+    kb_id       varchar(32) not null comment '知识库ID',
+    user_id     bigint(20)  not null comment '用户ID',
+    name        varchar(64) comment '知识库名称',
+    profile     varchar(256) comment '知识库简介',
+    url         varchar(256) comment '知识库url地址',
+    keywords    varchar(256) comment '关键词',
+    cover       varchar(256) comment '封面链接',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default '' comment '备注',
+    primary key (kb_id)
+) engine = innodb comment = '知识库表';
+
+-- ----------------------------
+-- 7、知识库-文档关联表
+-- ----------------------------
+drop table if exists sys_kb_file;
+create table sys_kb_file
+(
+    kb_id       varchar(32) not null comment '知识库ID',
+    file_id     varchar(32) not null comment '文档ID',
+    user_id     bigint(20)  not null comment '用户ID',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default '' comment '备注'
+) engine = innodb comment = '知识库-文档关联表';
+
+-- ----------------------------
+-- 8、图谱表
+-- ----------------------------
+drop table if exists sys_kg;
+create table sys_kg
+(
+    kg_id       varchar(32)  not null comment '图谱ID',
+    kb_id       varchar(32)  not null comment '知识库ID',
+    file_ids    varchar(500) not null comment '文档ID列表,全选时为1,没选时为0,选择部分时为文件id列表,用逗号隔开',
+    user_id     bigint(20)   not null comment '用户ID',
+    name        varchar(64)  not null comment '图谱名称',
+    profile     varchar(256) comment '图谱简介',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default '' comment '备注',
+    primary key (kg_id)
+) engine = innodb comment = '图谱表';
+
+-- ----------------------------
+-- 9、图谱实体表
+-- ----------------------------
+drop table if exists sys_entity;
+create table sys_entity
+(
+    entity_id   varchar(32) not null comment '实体ID',
+    kg_id       varchar(32) not null comment '图谱ID',
+    user_id     bigint(20)  not null comment '用户ID',
+    name        varchar(64) not null comment '实体名称',
+    nick_name   varchar(64) comment '实体别称',
+    profile     varchar(256) comment '实体简介',
+    type        varchar(32) comment '实体类型',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default '' comment '备注',
+    primary key (entity_id)
+) engine = innodb comment = '图谱实体表';
+
+-- ----------------------------
+-- 9、图谱实体实例表
+-- ----------------------------
+drop table if exists sys_entity_instance;
+create table sys_entity_instance
+(
+    instance_id varchar(32) not null comment '实例ID',
+    entity_id   varchar(32) not null comment '实体ID',
+    user_id     bigint(20)  not null comment '用户ID',
+    name        varchar(64) not null comment '实例名称',
+    nick_name   varchar(256) comment '实例别名,多个别名用逗号隔开',
+    create_by   varchar(64)  default '' comment '创建者',
+    create_time datetime comment '创建时间',
+    update_by   varchar(64)  default '' comment '更新者',
+    update_time datetime comment '更新时间',
+    remark      varchar(512) default '' comment '备注',
+    primary key (entity_id)
+) engine = innodb comment = '图谱实体表';
+
+

+ 21 - 0
src/main/java/com/pavis/backend/slim/BackendSlimApplication.java

@@ -0,0 +1,21 @@
+package com.pavis.backend.slim;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+/**
+ * 程序入口
+ *
+ * @author semi
+ */
+@MapperScan("com.pavis.backend.slim.project.system.mapper")
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+public class BackendSlimApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(BackendSlimApplication.class, args);
+    }
+
+}

+ 76 - 0
src/main/java/com/pavis/backend/slim/common/constant/Constant.java

@@ -0,0 +1,76 @@
+package com.pavis.backend.slim.common.constant;
+
+/**
+ * @author semi
+ * @create 2023-04-20 16:59
+ */
+public class Constant {
+
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+
+    /**
+     * 登录用户 key
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    /**
+     * 登录用户 redis key
+     */
+    public static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+
+    /**
+     * 登录账户密码错误次数 redis key
+     */
+    public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
+
+    /**
+     * 用户名长度限制
+     */
+    public static final int USERNAME_MIN_LENGTH = 2;
+    public static final int USERNAME_MAX_LENGTH = 20;
+
+    /**
+     * 密码长度限制
+     */
+    public static final int PASSWORD_MIN_LENGTH = 5;
+    public static final int PASSWORD_MAX_LENGTH = 20;
+
+    /**
+     * 防重提交 redis key
+     */
+    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 文件夹分隔符
+     */
+    public static final String DIR_SEP = "/";
+
+    /**
+     * 点分隔符
+     */
+    public static final String POINT_SEP = ".";
+
+    /**
+     * 小文件最大大小: 5MB = 1024 * 1024 * 5
+     */
+    public static final long FILE_MAX_SIZE = 5242880L;
+}

+ 94 - 0
src/main/java/com/pavis/backend/slim/common/constant/HttpStatus.java

@@ -0,0 +1,94 @@
+package com.pavis.backend.slim.common.constant;
+
+/**
+ * 返回状态码
+ * 
+ * @author semi
+ */
+public class HttpStatus
+{
+    /**
+     * 操作成功
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 对象创建成功
+     */
+    public static final int CREATED = 201;
+
+    /**
+     * 请求已经被接受
+     */
+    public static final int ACCEPTED = 202;
+
+    /**
+     * 操作已经执行成功,但是没有返回数据
+     */
+    public static final int NO_CONTENT = 204;
+
+    /**
+     * 资源已被移除
+     */
+    public static final int MOVED_PERM = 301;
+
+    /**
+     * 重定向
+     */
+    public static final int SEE_OTHER = 303;
+
+    /**
+     * 资源没有被修改
+     */
+    public static final int NOT_MODIFIED = 304;
+
+    /**
+     * 参数列表错误(缺少,格式不匹配)
+     */
+    public static final int BAD_REQUEST = 400;
+
+    /**
+     * 未授权
+     */
+    public static final int UNAUTHORIZED = 401;
+
+    /**
+     * 访问受限,授权过期
+     */
+    public static final int FORBIDDEN = 403;
+
+    /**
+     * 资源,服务未找到
+     */
+    public static final int NOT_FOUND = 404;
+
+    /**
+     * 不允许的http方法
+     */
+    public static final int BAD_METHOD = 405;
+
+    /**
+     * 资源冲突,或者资源被锁
+     */
+    public static final int CONFLICT = 409;
+
+    /**
+     * 不支持的数据,媒体类型
+     */
+    public static final int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 系统内部错误
+     */
+    public static final int ERROR = 500;
+
+    /**
+     * 接口未实现
+     */
+    public static final int NOT_IMPLEMENTED = 501;
+
+    /**
+     * 系统警告消息
+     */
+    public static final int WARN = 601;
+}

+ 49 - 0
src/main/java/com/pavis/backend/slim/common/exception/GlobalException.java

@@ -0,0 +1,49 @@
+package com.pavis.backend.slim.common.exception;
+
+/**
+ * 全局异常
+ *
+ * @author semi
+ */
+public class GlobalException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     */
+    private String detailMessage;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public GlobalException() {
+    }
+
+    public GlobalException(String message) {
+        this.message = message;
+    }
+
+    public String getDetailMessage() {
+        return detailMessage;
+    }
+
+    public GlobalException setDetailMessage(String detailMessage) {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public GlobalException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+}

+ 63 - 0
src/main/java/com/pavis/backend/slim/common/exception/ServiceException.java

@@ -0,0 +1,63 @@
+package com.pavis.backend.slim.common.exception;
+
+/**
+ * 业务异常
+ *
+ * @author semi
+ */
+public final class ServiceException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private Integer code;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误明细,内部调试错误
+     */
+    private String detailMessage;
+
+    /**
+     * 空构造方法,避免反序列化问题
+     */
+    public ServiceException() {
+    }
+
+    public ServiceException(String message) {
+        this.message = message;
+    }
+
+    public ServiceException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public String getDetailMessage() {
+        return detailMessage;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public ServiceException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public ServiceException setDetailMessage(String detailMessage) {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+}

+ 22 - 0
src/main/java/com/pavis/backend/slim/common/exception/UtilException.java

@@ -0,0 +1,22 @@
+package com.pavis.backend.slim.common.exception;
+
+/**
+ * 工具类异常
+ *
+ * @author semi
+ */
+public class UtilException extends RuntimeException {
+    private static final long serialVersionUID = 8247610319171014183L;
+
+    public UtilException(Throwable e) {
+        super(e.getMessage(), e);
+    }
+
+    public UtilException(String message) {
+        super(message);
+    }
+
+    public UtilException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}

+ 84 - 0
src/main/java/com/pavis/backend/slim/common/exception/base/BaseException.java

@@ -0,0 +1,84 @@
+package com.pavis.backend.slim.common.exception.base;
+
+import com.pavis.backend.slim.common.utils.MessageUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 基础异常
+ *
+ * @author semi
+ */
+public class BaseException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块
+     */
+    private String module;
+
+    /**
+     * 错误码
+     */
+    private String code;
+
+    /**
+     * 错误码对应的参数
+     */
+    private Object[] args;
+
+    /**
+     * 错误消息
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args, String defaultMessage) {
+        this.module = module;
+        this.code = code;
+        this.args = args;
+        this.defaultMessage = defaultMessage;
+    }
+
+    public BaseException(String module, String code, Object[] args) {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage) {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args) {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage) {
+        this(null, null, null, defaultMessage);
+    }
+
+    @Override
+    public String getMessage() {
+        String message = null;
+        if (!StringUtils.isEmpty(code)) {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null) {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public Object[] getArgs() {
+        return args;
+    }
+
+    public String getDefaultMessage() {
+        return defaultMessage;
+    }
+}

+ 17 - 0
src/main/java/com/pavis/backend/slim/common/exception/file/FileException.java

@@ -0,0 +1,17 @@
+package com.pavis.backend.slim.common.exception.file;
+
+import com.pavis.backend.slim.common.exception.base.BaseException;
+
+/**
+ * 文件信息异常类
+ *
+ * @author semi
+ */
+public class FileException extends BaseException {
+    private static final long serialVersionUID = 1L;
+
+    public FileException(String code, Object[] args) {
+        super("file", code, args, null);
+    }
+
+}

+ 52 - 0
src/main/java/com/pavis/backend/slim/common/exception/file/FileUploadException.java

@@ -0,0 +1,52 @@
+package com.pavis.backend.slim.common.exception.file;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * 文件上传异常类
+ *
+ * @author semi
+ */
+public class FileUploadException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Throwable cause;
+
+    public FileUploadException() {
+        this(null, null);
+    }
+
+    public FileUploadException(final String msg) {
+        this(msg, null);
+    }
+
+    public FileUploadException(String msg, Throwable cause) {
+        super(msg);
+        this.cause = cause;
+    }
+
+    @Override
+    public void printStackTrace(PrintStream stream) {
+        super.printStackTrace(stream);
+        if (cause != null) {
+            stream.println("Caused by:");
+            cause.printStackTrace(stream);
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintWriter writer) {
+        super.printStackTrace(writer);
+        if (cause != null) {
+            writer.println("Caused by:");
+            cause.printStackTrace(writer);
+        }
+    }
+
+    @Override
+    public Throwable getCause() {
+        return cause;
+    }
+}

+ 29 - 0
src/main/java/com/pavis/backend/slim/common/exception/job/TaskException.java

@@ -0,0 +1,29 @@
+package com.pavis.backend.slim.common.exception.job;
+
+/**
+ * 计划策略异常
+ *
+ * @author semi
+ */
+public class TaskException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    private Code code;
+
+    public TaskException(String msg, Code code) {
+        this(msg, code, null);
+    }
+
+    public TaskException(String msg, Code code, Exception nestedEx) {
+        super(msg, nestedEx);
+        this.code = code;
+    }
+
+    public Code getCode() {
+        return code;
+    }
+
+    public enum Code {
+        TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE
+    }
+}

+ 16 - 0
src/main/java/com/pavis/backend/slim/common/exception/user/UserException.java

@@ -0,0 +1,16 @@
+package com.pavis.backend.slim.common.exception.user;
+
+import com.pavis.backend.slim.common.exception.base.BaseException;
+
+/**
+ * 用户信息异常类
+ *
+ * @author semi
+ */
+public class UserException extends BaseException {
+    private static final long serialVersionUID = 1L;
+
+    public UserException(String code, Object[] args) {
+        super("user", code, args, null);
+    }
+}

+ 14 - 0
src/main/java/com/pavis/backend/slim/common/exception/user/UserNotExistsException.java

@@ -0,0 +1,14 @@
+package com.pavis.backend.slim.common.exception.user;
+
+/**
+ * 用户不存在异常类
+ *
+ * @author semi
+ */
+public class UserNotExistsException extends UserException {
+    private static final long serialVersionUID = 1L;
+
+    public UserNotExistsException() {
+        super("user.not.exists", null);
+    }
+}

+ 14 - 0
src/main/java/com/pavis/backend/slim/common/exception/user/UserPasswordNotMatchException.java

@@ -0,0 +1,14 @@
+package com.pavis.backend.slim.common.exception.user;
+
+/**
+ * 用户密码不正确或不符合规范异常类
+ *
+ * @author semi
+ */
+public class UserPasswordNotMatchException extends UserException {
+    private static final long serialVersionUID = 1L;
+
+    public UserPasswordNotMatchException() {
+        super("user.password.not.match", null);
+    }
+}

+ 14 - 0
src/main/java/com/pavis/backend/slim/common/exception/user/UserPasswordRetryLimitExceedException.java

@@ -0,0 +1,14 @@
+package com.pavis.backend.slim.common.exception.user;
+
+/**
+ * 用户错误最大次数异常类
+ *
+ * @author semi
+ */
+public class UserPasswordRetryLimitExceedException extends UserException {
+    private static final long serialVersionUID = 1L;
+
+    public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) {
+        super("user.password.retry.limit.exceed", new Object[]{retryLimitCount, lockTime});
+    }
+}

+ 133 - 0
src/main/java/com/pavis/backend/slim/common/utils/FileUtils.java

@@ -0,0 +1,133 @@
+package com.pavis.backend.slim.common.utils;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.project.system.domain.vo.TreeFile;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author semi
+ * @create 2023-04-28 15:44
+ */
+public class FileUtils {
+
+    /**
+     * 文档类型
+     */
+    public static final Integer TYPE_DOC = 1;
+
+    /**
+     * 图片类型
+     */
+    public static final Integer TYPE_IMG = 2;
+
+    /**
+     * 音频类型
+     */
+    public static final Integer TYPE_VOL = 3;
+
+    /**
+     * 视频类型
+     */
+    public static final Integer TYPE_VID = 4;
+
+    /**
+     * 文件夹类型
+     */
+    public static final Integer TYPE_DIR = -1;
+
+    /**
+     * 其他类型
+     */
+    public static final Integer TYPE_NON = 0;
+
+    /**
+     * 文档类型
+     */
+    public static final String[] DOC_EXT = {"doc", "docx", "pdf", "xls", "xlsx", "ppt", "pptx", "txt", "md"};
+
+    /**
+     * 图片类型
+     */
+    public static final String[] IMG_EXT = {"bmp", "gif", "jpg", "jpeg", "png"};
+
+    /**
+     * 音频类型
+     */
+    public static final String[] VOL_EXT = {"mp3", "flac", "acc", "m4a", "wma", "ac3", "m4r", "amr"};
+
+    /**
+     * 视频类型
+     */
+    public static final String[] VID_EXT = {"mp4", "avi", "mkv", "mov", "m4v", "flv", "vob", "rmvb"};
+
+    /**
+     * 根据原文件名生成系统文件名称
+     *
+     * @param originalName 原文件名称
+     * @return 系统文件名称
+     */
+    public static String genFileName(String originalName) {
+        if (StringUtils.contains(originalName, Constant.POINT_SEP)) {
+            StringBuilder builder = StrUtil.builder();
+            return builder
+                    .append(IdUtil.simpleUUID())
+                    .append(Constant.POINT_SEP)
+                    .append(StrUtil.subAfter(originalName, Constant.POINT_SEP, true))
+                    .toString();
+        } else {
+            return IdUtil.simpleUUID();
+        }
+    }
+
+    /**
+     * 获取文件类型
+     * 文件类型
+     * 0. 其他
+     * 1. 文档
+     * 2. 图片
+     * 3. 音频
+     * 4. 视频
+     *
+     * @param suffix 文件后缀
+     * @return 文件类型
+     */
+    public static int getFileType(String suffix) {
+        if (StringUtils.isNotBlank(suffix)) {
+            if (ArrayUtil.containsIgnoreCase(DOC_EXT, suffix)) {
+                return TYPE_DOC;
+            }
+            if (ArrayUtil.containsIgnoreCase(IMG_EXT, suffix)) {
+                return TYPE_IMG;
+            }
+            if (ArrayUtil.containsIgnoreCase(VOL_EXT, suffix)) {
+                return TYPE_VOL;
+            }
+            if (ArrayUtil.containsIgnoreCase(VID_EXT, suffix)) {
+                return TYPE_VID;
+            }
+        }
+        return TYPE_NON;
+    }
+
+    /**
+     * 递归查询子节点
+     *
+     * @param root 根节点
+     * @param all  所有节点
+     * @return 根节点信息
+     */
+    public static List<TreeFile> getChildrens(TreeFile root, List<TreeFile> all) {
+        return all
+                .stream()
+                .filter(m -> Objects.equals(m.getParentId(), root.getId()))
+                .peek((m) -> m.setChildList(getChildrens(m, all)))
+                .collect(Collectors.toList());
+    }
+}

+ 25 - 0
src/main/java/com/pavis/backend/slim/common/utils/MessageUtils.java

@@ -0,0 +1,25 @@
+package com.pavis.backend.slim.common.utils;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ * 
+ * @author semi
+ */
+public class MessageUtils
+{
+    /**
+     * 根据消息键和参数 获取消息 委托给spring messageSource
+     *
+     * @param code 消息键
+     * @param args 参数
+     * @return 获取国际化翻译值
+     */
+    public static String message(String code, Object... args)
+    {
+        MessageSource messageSource = SpringUtils.getBean(MessageSource.class);
+        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
+    }
+}

+ 79 - 0
src/main/java/com/pavis/backend/slim/common/utils/SecurityUtils.java

@@ -0,0 +1,79 @@
+package com.pavis.backend.slim.common.utils;
+
+import com.pavis.backend.slim.common.constant.HttpStatus;
+import com.pavis.backend.slim.common.exception.ServiceException;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * 安全服务工具类
+ *
+ * @author semi
+ */
+public class SecurityUtils {
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication() {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    /**
+     * 用户ID
+     **/
+    public static Long getUserId() {
+        try {
+            return getLoginUser().getUserId();
+        } catch (Exception e) {
+            throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 用户名
+     **/
+    public static String getUsername() {
+        try {
+            return getLoginUser().getUsername();
+        } catch (Exception e) {
+            throw new ServiceException("获取用户名异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUser getLoginUser() {
+        try {
+            return (LoginUser) getAuthentication().getPrincipal();
+        } catch (Exception e) {
+            throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 生成BCryptPasswordEncoder密码
+     *
+     * @param password 密码
+     * @return 加密字符串
+     */
+    public static String encryptPassword(String password) {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.encode(password);
+    }
+
+    /**
+     * 判断密码是否相同
+     *
+     * @param rawPassword     真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+}

+ 172 - 0
src/main/java/com/pavis/backend/slim/common/utils/ServletUtils.java

@@ -0,0 +1,172 @@
+package com.pavis.backend.slim.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 客户端工具类
+ * 
+ * @author semi
+ */
+public class ServletUtils
+{
+    /**
+     * 获得所有请求参数
+     *
+     * @param request 请求对象{@link ServletRequest}
+     * @return Map
+     */
+    public static Map<String, String[]> getParams(ServletRequest request)
+    {
+        final Map<String, String[]> map = request.getParameterMap();
+        return Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * 获得所有请求参数
+     *
+     * @param request 请求对象{@link ServletRequest}
+     * @return Map
+     */
+    public static Map<String, String> getParamMap(ServletRequest request)
+    {
+        Map<String, String> params = new HashMap<>();
+        for (Map.Entry<String, String[]> entry : getParams(request).entrySet())
+        {
+            params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));
+        }
+        return params;
+    }
+
+    /**
+     * 获取request
+     */
+    public static HttpServletRequest getRequest()
+    {
+        return getRequestAttributes().getRequest();
+    }
+
+    /**
+     * 获取response
+     */
+    public static HttpServletResponse getResponse()
+    {
+        return getRequestAttributes().getResponse();
+    }
+
+    /**
+     * 获取session
+     */
+    public static HttpSession getSession()
+    {
+        return getRequest().getSession();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes()
+    {
+        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+        return (ServletRequestAttributes) attributes;
+    }
+
+    /**
+     * 将字符串渲染到客户端
+     * 
+     * @param response 渲染对象
+     * @param string 待渲染的字符串
+     */
+    public static void renderString(HttpServletResponse response, String string)
+    {
+        try
+        {
+            response.setStatus(200);
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(string);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取客户端IP
+     *
+     * @param request 请求对象
+     * @return IP地址
+     */
+    public static String getIpAddr()
+    {
+        HttpServletRequest request = getRequest();
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getRemoteAddr();
+        }
+
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
+    }
+
+    /**
+     * 从多级反向代理中获得第一个非unknown IP地址
+     *
+     * @param ip 获得的IP地址
+     * @return 第一个非unknown IP地址
+     */
+    public static String getMultistageReverseProxyIp(String ip)
+    {
+        // 多级反向代理检测
+        if (ip != null && ip.indexOf(",") > 0)
+        {
+            final String[] ips = ip.trim().split(",");
+            for (String subIp : ips)
+            {
+                if (!isUnknown(subIp))
+                {
+                    ip = subIp;
+                    break;
+                }
+            }
+        }
+        return StringUtils.substring(ip, 0, 255);
+    }
+
+    /**
+     * 检测给定字符串是否为未知,多用于检测HTTP请求相关
+     *
+     * @param checkString 被检测的字符串
+     * @return 是否未知
+     */
+    public static boolean isUnknown(String checkString)
+    {
+        return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
+    }
+}

+ 142 - 0
src/main/java/com/pavis/backend/slim/common/utils/SpringUtils.java

@@ -0,0 +1,142 @@
+package com.pavis.backend.slim.common.utils;
+
+
+import cn.hutool.core.util.ArrayUtil;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring工具类 方便在非spring管理环境中获取bean
+ *
+ * @author semi
+ */
+@Component
+public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
+    /**
+     * Spring应用上下文环境
+     */
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+        SpringUtils.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringUtils.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取对象
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws BeansException
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) throws BeansException {
+        return (T) beanFactory.getBean(name);
+    }
+
+    /**
+     * 获取类型为requiredType的对象
+     *
+     * @param clz
+     * @return
+     * @throws BeansException
+     */
+    public static <T> T getBean(Class<T> clz) throws BeansException {
+        T result = (T) beanFactory.getBean(clz);
+        return result;
+    }
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name
+     * @return boolean
+     */
+    public static boolean containsBean(String name) {
+        return beanFactory.containsBean(name);
+    }
+
+    /**
+     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+     *
+     * @param name
+     * @return boolean
+     * @throws NoSuchBeanDefinitionException
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+        return beanFactory.isSingleton(name);
+    }
+
+    /**
+     * @param name
+     * @return Class 注册对象的类型
+     * @throws NoSuchBeanDefinitionException
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
+        return beanFactory.getType(name);
+    }
+
+    /**
+     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+     *
+     * @param name
+     * @return
+     * @throws NoSuchBeanDefinitionException
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+        return beanFactory.getAliases(name);
+    }
+
+    /**
+     * 获取aop代理对象
+     *
+     * @param invoker
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker) {
+        return (T) AopContext.currentProxy();
+    }
+
+    /**
+     * 获取当前的环境配置,无配置返回null
+     *
+     * @return 当前的环境配置
+     */
+    public static String[] getActiveProfiles() {
+        return applicationContext.getEnvironment().getActiveProfiles();
+    }
+
+    /**
+     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
+     *
+     * @return 当前的环境配置
+     */
+    public static String getActiveProfile() {
+        final String[] activeProfiles = getActiveProfiles();
+        return ArrayUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
+    }
+
+    /**
+     * 获取配置文件中的值
+     *
+     * @param key 配置文件的key
+     * @return 当前的配置文件的值
+     */
+    public static String getRequiredProperty(String key) {
+        return applicationContext.getEnvironment().getRequiredProperty(key);
+    }
+}

+ 26 - 0
src/main/java/com/pavis/backend/slim/common/xss/Xss.java

@@ -0,0 +1,26 @@
+package com.pavis.backend.slim.common.xss;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 自定义xss校验注解
+ *
+ * @author semi
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
+@Constraint(validatedBy = {XssValidator.class})
+public @interface Xss {
+    String message()
+
+            default "不允许任何脚本运行";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 31 - 0
src/main/java/com/pavis/backend/slim/common/xss/XssValidator.java

@@ -0,0 +1,31 @@
+package com.pavis.backend.slim.common.xss;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 自定义xss校验注解实现
+ *
+ * @author semi
+ */
+public class XssValidator implements ConstraintValidator<Xss, String> {
+    private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />";
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StringUtils.isBlank(value)) {
+            return true;
+        }
+        return !containsHtml(value);
+    }
+
+    public static boolean containsHtml(String value) {
+        Pattern pattern = Pattern.compile(HTML_PATTERN);
+        Matcher matcher = pattern.matcher(value);
+        return matcher.matches();
+    }
+}

+ 14 - 0
src/main/java/com/pavis/backend/slim/framework/annotation/Anonymous.java

@@ -0,0 +1,14 @@
+package com.pavis.backend.slim.framework.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 匿名访问不鉴权注解
+ *
+ * @author semi
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Anonymous {
+}

+ 14 - 0
src/main/java/com/pavis/backend/slim/framework/config/BackendSlimConfig.java

@@ -0,0 +1,14 @@
+package com.pavis.backend.slim.framework.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 项目相关配置文件
+ * @author semi
+ * @create 2023-04-20 17:01
+ */
+@Configuration
+@ConfigurationProperties(prefix = "pavis")
+public class BackendSlimConfig {
+}

+ 28 - 0
src/main/java/com/pavis/backend/slim/framework/config/MinioConfig.java

@@ -0,0 +1,28 @@
+package com.pavis.backend.slim.framework.config;
+
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * minio对象存储配置文件
+ *
+ * @author semi
+ * @create 2023-04-22 14:23
+ */
+@Configuration
+public class MinioConfig {
+    @Value("${minio.url}")
+    private String url;
+    @Value("${minio.accessKey}")
+    private String accessKey;
+    @Value("${minio.secretKey}")
+    private String secretKey;
+
+    @Bean
+    public MinioClient client() {
+        return MinioClient.builder().endpoint(url)
+                .credentials(accessKey, secretKey).build();
+    }
+}

+ 26 - 0
src/main/java/com/pavis/backend/slim/framework/config/MyBatisPlusConfig.java

@@ -0,0 +1,26 @@
+package com.pavis.backend.slim.framework.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatisPlus配置类
+ *
+ * @author semi
+ * @create 2023-04-21 10:05
+ */
+@Configuration
+public class MyBatisPlusConfig {
+
+    /**
+     * 分页拦截器配置
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+        return interceptor;
+    }
+}

+ 53 - 0
src/main/java/com/pavis/backend/slim/framework/config/ResourcesConfig.java

@@ -0,0 +1,53 @@
+package com.pavis.backend.slim.framework.config;
+
+import com.pavis.backend.slim.framework.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 通用资源配置
+ *
+ * @author semi
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    /**
+     * 自定义拦截规则
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    }
+
+    /**
+     * 跨域配置
+     */
+    @Bean
+    public CorsFilter corsFilter() {
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        // 设置访问源地址
+        config.addAllowedOriginPattern("*");
+        // 设置访问源请求头
+        config.addAllowedHeader("*");
+        // 设置访问源请求方法
+        config.addAllowedMethod("*");
+        // 有效期 1800秒
+        config.setMaxAge(1800L);
+        // 添加映射路径,拦截一切请求
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", config);
+        // 返回新的CorsFilter
+        return new CorsFilter(source);
+    }
+}

+ 141 - 0
src/main/java/com/pavis/backend/slim/framework/config/SecurityConfig.java

@@ -0,0 +1,141 @@
+package com.pavis.backend.slim.framework.config;
+
+import com.pavis.backend.slim.framework.config.properties.PermitAllUrlProperties;
+import com.pavis.backend.slim.framework.security.filter.JwtAuthenticationTokenFilter;
+import com.pavis.backend.slim.framework.security.handle.AuthenticationEntryPointImpl;
+import com.pavis.backend.slim.framework.security.handle.LogoutSuccessHandlerImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.filter.CorsFilter;
+
+/**
+ * spring security配置
+ *
+ * @author semi
+ */
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    /**
+     * 自定义用户认证逻辑
+     */
+    @Autowired
+    private UserDetailsService userDetailsService;
+
+    /**
+     * 认证失败处理类
+     */
+    @Autowired
+    private AuthenticationEntryPointImpl unauthorizedHandler;
+
+    /**
+     * 退出处理类
+     */
+    @Autowired
+    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+
+    /**
+     * token认证过滤器
+     */
+    @Autowired
+    private JwtAuthenticationTokenFilter authenticationTokenFilter;
+
+    /**
+     * 跨域过滤器
+     */
+    @Autowired
+    private CorsFilter corsFilter;
+
+    /**
+     * 允许匿名访问的地址
+     */
+    @Autowired
+    private PermitAllUrlProperties permitAllUrl;
+
+    /**
+     * 强散列哈希加密实现
+     */
+    @Bean
+    public BCryptPasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    /**
+     * 直接注入 AuthenticationManager
+     */
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
+    }
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        // 注解标记允许匿名访问的url
+        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
+        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
+        http
+                // CSRF禁用,因为不使用session
+                .csrf().disable()
+                // 禁用HTTP响应标头
+                .headers().cacheControl().disable().and()
+                // 认证失败处理类
+                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+                // 基于token,所以不需要session
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+                // 过滤请求
+                .authorizeRequests()
+                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
+                .antMatchers("/login", "/register").permitAll()
+                // 静态资源,可匿名访问
+                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
+                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+                // 除上面外的所有请求全部需要鉴权认证
+                .anyRequest().authenticated()
+                .and()
+                .headers().frameOptions().disable();
+        // 添加Logout filter
+        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
+        // 添加JWT filter
+        http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+        // 添加CORS filter
+        http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
+        http.addFilterBefore(corsFilter, LogoutFilter.class);
+    }
+}

+ 65 - 0
src/main/java/com/pavis/backend/slim/framework/config/properties/PermitAllUrlProperties.java

@@ -0,0 +1,65 @@
+package com.pavis.backend.slim.framework.config.properties;
+
+import com.pavis.backend.slim.framework.annotation.Anonymous;
+import org.apache.commons.lang3.RegExUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * 设置Anonymous注解允许匿名访问的url
+ *
+ * @author semi
+ */
+@Configuration
+public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware {
+    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
+
+    private ApplicationContext applicationContext;
+
+    private List<String> urls = new ArrayList<>();
+
+    public String ASTERISK = "*";
+
+    @Override
+    public void afterPropertiesSet() {
+        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
+        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
+
+        map.keySet().forEach(info -> {
+            HandlerMethod handlerMethod = map.get(info);
+
+            // 获取方法上边的注解 替代path variable 为 *
+            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
+            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
+                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
+
+            // 获取类上边的注解, 替代path variable 为 *
+            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
+            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
+                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
+        });
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        this.applicationContext = context;
+    }
+
+    public List<String> getUrls() {
+        return urls;
+    }
+
+    public void setUrls(List<String> urls) {
+        this.urls = urls;
+    }
+}

+ 45 - 0
src/main/java/com/pavis/backend/slim/framework/interceptor/RepeatSubmitInterceptor.java

@@ -0,0 +1,45 @@
+package com.pavis.backend.slim.framework.interceptor;
+
+import com.alibaba.fastjson2.JSON;
+import com.pavis.backend.slim.common.utils.ServletUtils;
+import com.pavis.backend.slim.framework.interceptor.annotation.RepeatSubmit;
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+/**
+ * 防止重复提交拦截器
+ *
+ * @author semi
+ */
+@Component
+public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        if (handler instanceof HandlerMethod) {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Method method = handlerMethod.getMethod();
+            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
+            if (annotation != null) {
+                if (this.isRepeatSubmit(request, annotation)) {
+                    AjaxResult ajaxResult = AjaxResult.error(annotation.message());
+                    ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 验证是否重复提交由子类实现具体的防重复提交的规则
+     */
+    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
+}

+ 26 - 0
src/main/java/com/pavis/backend/slim/framework/interceptor/annotation/RepeatSubmit.java

@@ -0,0 +1,26 @@
+package com.pavis.backend.slim.framework.interceptor.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义注解防止表单重复提交
+ * 
+ * @author semi
+ *
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit
+{
+    /**
+     * 间隔时间(ms),小于此时间视为重复提交
+     */
+    public int interval() default 5000;
+
+    /**
+     * 提示消息
+     */
+    public String message() default "不允许重复提交,请稍后再试";
+}

+ 97 - 0
src/main/java/com/pavis/backend/slim/framework/interceptor/impl/SameUrlDataInterceptor.java

@@ -0,0 +1,97 @@
+package com.pavis.backend.slim.framework.interceptor.impl;
+
+import cn.hutool.extra.servlet.ServletUtil;
+import com.alibaba.fastjson2.JSON;
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.framework.interceptor.RepeatSubmitInterceptor;
+import com.pavis.backend.slim.framework.interceptor.annotation.RepeatSubmit;
+import com.pavis.backend.slim.framework.redis.RedisCache;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 判断请求url和数据是否和上一次相同,
+ * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
+ *
+ * @author semi
+ */
+@Component
+public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
+    public final String REPEAT_PARAMS = "repeatParams";
+
+    public final String REPEAT_TIME = "repeatTime";
+
+    /**
+     * 令牌自定义标识
+     */
+    @Value("${token.header}")
+    private String header;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
+        String nowParams = ServletUtil.getBody(request);
+        // body参数为空,获取Parameter的数据
+        if (StringUtils.isEmpty(nowParams)) {
+            nowParams = JSON.toJSONString(request.getParameterMap());
+        }
+        Map<String, Object> nowDataMap = new HashMap<String, Object>();
+        nowDataMap.put(REPEAT_PARAMS, nowParams);
+        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
+
+        // 唯一标识(指定key + url + 消息头)
+        String cacheRepeatKey = Constant.REPEAT_SUBMIT_KEY + url + submitKey;
+
+        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
+        if (sessionObj != null) {
+            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
+            if (sessionMap.containsKey(url)) {
+                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
+                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) {
+                    return true;
+                }
+            }
+        }
+        Map<String, Object> cacheMap = new HashMap<String, Object>();
+        cacheMap.put(url, nowDataMap);
+        redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
+        return false;
+    }
+
+    /**
+     * 判断参数是否相同
+     */
+    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
+        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
+        String preParams = (String) preMap.get(REPEAT_PARAMS);
+        return nowParams.equals(preParams);
+    }
+
+    /**
+     * 判断两次间隔时间
+     */
+    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval) {
+        long time1 = (Long) nowMap.get(REPEAT_TIME);
+        long time2 = (Long) preMap.get(REPEAT_TIME);
+        if ((time1 - time2) < interval) {
+            return true;
+        }
+        return false;
+    }
+}

+ 179 - 0
src/main/java/com/pavis/backend/slim/framework/minio/MinioStorage.java

@@ -0,0 +1,179 @@
+package com.pavis.backend.slim.framework.minio;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.common.exception.UtilException;
+import io.minio.*;
+import io.minio.errors.MinioException;
+import io.minio.http.Method;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * minio 工具类
+ *
+ * @author semi
+ * @create 2023-04-22 14:28
+ */
+@Component
+public class MinioStorage {
+
+    @Autowired
+    private MinioClient client;
+
+    @Value("${minio.bucketName}")
+    private String bucketName;
+
+    @Value("${minio.url}")
+    private String endpoint;
+
+    /**
+     * 判断bucket是否存在
+     *
+     * @param bucketName 需要判断的bucket
+     */
+    public boolean checkBucketExist(String bucketName) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
+        try {
+            return client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
+        } catch (MinioException e) {
+            throw new UtilException(e.getMessage());
+        }
+    }
+
+    /**
+     * 根据bucketName创建bucket
+     *
+     * @param bucketName bucket name
+     */
+    public void createBucket(String bucketName) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
+        if (!checkBucketExist(bucketName)) {
+            try {
+                client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+            } catch (MinioException e) {
+                throw new UtilException(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 上传文件至指定bucket或指定文件夹
+     *
+     * @param key 文件键值
+     * @param is  文件input流
+     */
+    public void putObject(String key, MultipartFile file) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
+        try {
+            // 1. 创建bucket
+            createBucket(bucketName);
+            // 2. 存储文件
+            client.putObject(PutObjectArgs.builder().bucket(bucketName).object(key).stream(file.getInputStream(), file.getSize(), -1).build());
+        } catch (MinioException e) {
+            throw new UtilException(e.getMessage());
+        }
+    }
+
+    /**
+     * 获取文件可访问url
+     *
+     * @param key 文件key值
+     * @return 可访问的url
+     */
+    public String preview(String key) {
+        // 查看文件地址
+        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(key).method(Method.GET).build();
+        try {
+            return client.getPresignedObjectUrl(build);
+        } catch (Exception e) {
+            throw new UtilException(e.getMessage());
+        }
+    }
+
+    /**
+     * 生成文件key值
+     * 例:1/avatar.jpg
+     *
+     * @param userId   用户id
+     * @param filename 文件名称
+     * @return 文件key值
+     */
+    public String genKey(Long userId, String filename) {
+        StringBuilder builder = StrUtil.builder();
+        return builder.append(userId)
+                .append(Constant.DIR_SEP)
+                .append(filename)
+                .toString();
+    }
+
+    /**
+     * 生成文件key值
+     * 例:1/20230423/avatar.jpg
+     *
+     * @param userId   用户id
+     * @param filename 文件名称
+     * @return 文件key值
+     */
+    public String genKeyWithDate(Long userId, String filename) {
+        StringBuilder builder = StrUtil.builder();
+        return builder.append(userId)
+                .append(Constant.DIR_SEP)
+                .append(DateUtil.format(DateUtil.date(), "yyyyMMdd"))
+                .append(Constant.DIR_SEP)
+                .append(filename)
+                .toString();
+    }
+
+    /**
+     * 生成文件key值
+     * 用户id/path/filename
+     * 例:1/a/b/avatar.jpg
+     * path示例: /a/b/或/
+     *
+     * @param userId   用户id
+     * @param filename 文件名称
+     * @param path     文件所属文件夹
+     * @return 文件key值
+     */
+    public String genKeyWithPath(Long userId, String filename, String path) {
+        StringBuilder builder = StrUtil.builder();
+        if (StringUtils.equals(path, Constant.DIR_SEP)) {
+            return genKey(userId, filename);
+        } else {
+            return builder.append(userId)
+                    .append(path)
+                    .append(filename)
+                    .toString();
+        }
+    }
+
+    /**
+     * 获取文件url
+     *
+     * @param objectKey 文件key
+     * @return 文件url
+     */
+    public String getObjectUrl(String objectKey) {
+        StringBuilder builder = StrUtil.builder();
+        return builder.append(endpoint)
+                .append(bucketName)
+                .append(Constant.DIR_SEP)
+                .append(objectKey)
+                .toString();
+    }
+
+    /**
+     * 获取bucketName
+     *
+     * @return bucketName
+     */
+    public String getBucketName() {
+        return bucketName;
+    }
+}

+ 243 - 0
src/main/java/com/pavis/backend/slim/framework/redis/RedisCache.java

@@ -0,0 +1,243 @@
+package com.pavis.backend.slim.framework.redis;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * spring redis 工具类
+ *
+ * @author semi
+ **/
+@SuppressWarnings(value = {"unchecked", "rawtypes"})
+@Component
+public class RedisCache {
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key   缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key      缓存的键值
+     * @param value    缓存的值
+     * @param timeout  时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key     Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout) {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key     Redis键
+     * @param timeout 超时时间
+     * @param unit    时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获取有效时间
+     *
+     * @param key Redis键
+     * @return 有效时间
+     */
+    public long getExpire(final String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 判断 key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key) {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key) {
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public boolean deleteObject(final Collection collection) {
+        return redisTemplate.delete(collection) > 0;
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key      缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList) {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key) {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key     缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext()) {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key   Redis键
+     * @param hKey  Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey) {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key   Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 删除Hash中的某条数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return 是否成功
+     */
+    public boolean deleteCacheMapValue(final String key, final String hKey) {
+        return redisTemplate.opsForHash().delete(key, hKey) > 0;
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+}

+ 38 - 0
src/main/java/com/pavis/backend/slim/framework/security/LoginBody.java

@@ -0,0 +1,38 @@
+package com.pavis.backend.slim.framework.security;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 用户登录对象
+ *
+ * @author semi
+ */
+public class LoginBody {
+    /**
+     * 用户名
+     */
+    @ApiModelProperty("用户名")
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @ApiModelProperty("密码")
+    private String password;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 168 - 0
src/main/java/com/pavis/backend/slim/framework/security/LoginUser.java

@@ -0,0 +1,168 @@
+package com.pavis.backend.slim.framework.security;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author semi
+ */
+public class LoginUser implements UserDetails {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 系统用户信息
+     */
+    private SysUser user;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Long getLoginTime() {
+        return loginTime;
+    }
+
+    public void setLoginTime(Long loginTime) {
+        this.loginTime = loginTime;
+    }
+
+    public LoginUser() {
+    }
+
+    public LoginUser(SysUser user, Set<String> permissions) {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    public LoginUser(Long userId, SysUser user, Set<String> permissions) {
+        this.userId = userId;
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    @JSONField(serialize = false)
+    @Override
+    public String getPassword() {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUserName();
+    }
+
+    /**
+     * 账户是否未过期,过期无法验证
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    /**
+     * 指定用户是否解锁,锁定的用户无法进行身份验证
+     *
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    /**
+     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
+     *
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    /**
+     * 是否可用 ,禁用的用户不能身份验证
+     *
+     * @return
+     */
+    @JSONField(serialize = false)
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    public Long getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Long expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions) {
+        this.permissions = permissions;
+    }
+
+    public SysUser getUser() {
+        return user;
+    }
+
+    public void setUser(SysUser user) {
+        this.user = user;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return null;
+    }
+}

+ 24 - 0
src/main/java/com/pavis/backend/slim/framework/security/context/AuthenticationContextHolder.java

@@ -0,0 +1,24 @@
+package com.pavis.backend.slim.framework.security.context;
+
+import org.springframework.security.core.Authentication;
+
+/**
+ * 身份验证信息
+ *
+ * @author semi
+ */
+public class AuthenticationContextHolder {
+    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
+
+    public static Authentication getContext() {
+        return contextHolder.get();
+    }
+
+    public static void setContext(Authentication context) {
+        contextHolder.set(context);
+    }
+
+    public static void clearContext() {
+        contextHolder.remove();
+    }
+}

+ 28 - 0
src/main/java/com/pavis/backend/slim/framework/security/context/PermissionContextHolder.java

@@ -0,0 +1,28 @@
+package com.pavis.backend.slim.framework.security.context;
+
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+/**
+ * 权限信息
+ *
+ * @author semi
+ */
+public class PermissionContextHolder {
+    private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
+
+    public static void setContext(String permission) {
+        RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
+                RequestAttributes.SCOPE_REQUEST);
+    }
+
+    public static String getContext() {
+        Object attr = RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
+        if (attr != null) {
+            if (attr instanceof String) {
+                return (String) attr;
+            }
+        }
+        return null;
+    }
+}

+ 41 - 0
src/main/java/com/pavis/backend/slim/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -0,0 +1,41 @@
+package com.pavis.backend.slim.framework.security.filter;
+
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import com.pavis.backend.slim.framework.security.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * token过滤器 验证token有效性
+ *
+ * @author semi
+ */
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+    @Autowired
+    private TokenService tokenService;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (loginUser != null && SecurityUtils.getAuthentication() == null) {
+            tokenService.verifyToken(loginUser);
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
+            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        }
+        chain.doFilter(request, response);
+    }
+}

+ 31 - 0
src/main/java/com/pavis/backend/slim/framework/security/handle/AuthenticationEntryPointImpl.java

@@ -0,0 +1,31 @@
+package com.pavis.backend.slim.framework.security.handle;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson2.JSON;
+import com.pavis.backend.slim.common.constant.HttpStatus;
+import com.pavis.backend.slim.common.utils.ServletUtils;
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+
+/**
+ * 认证失败处理类 返回未授权
+ *
+ * @author semi
+ */
+@Component
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
+    private static final long serialVersionUID = -8970718410437077606L;
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
+        int code = HttpStatus.UNAUTHORIZED;
+        String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
+    }
+}

+ 39 - 0
src/main/java/com/pavis/backend/slim/framework/security/handle/LogoutSuccessHandlerImpl.java

@@ -0,0 +1,39 @@
+package com.pavis.backend.slim.framework.security.handle;
+
+import com.alibaba.fastjson2.JSON;
+import com.pavis.backend.slim.common.utils.ServletUtils;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import com.pavis.backend.slim.framework.security.service.TokenService;
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 自定义退出处理类 返回成功
+ *
+ * @author semi
+ */
+@Configuration
+public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
+
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 退出处理
+     */
+    @Override
+    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+        LoginUser loginUser = tokenService.getLoginUser(request);
+        if (loginUser != null) {
+            // 删除用户缓存记录
+            tokenService.delLoginUser(loginUser.getToken());
+        }
+        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
+    }
+}

+ 106 - 0
src/main/java/com/pavis/backend/slim/framework/security/service/LoginService.java

@@ -0,0 +1,106 @@
+package com.pavis.backend.slim.framework.security.service;
+
+import cn.hutool.core.date.DateUtil;
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.common.exception.ServiceException;
+import com.pavis.backend.slim.common.exception.user.UserNotExistsException;
+import com.pavis.backend.slim.common.exception.user.UserPasswordNotMatchException;
+import com.pavis.backend.slim.common.utils.ServletUtils;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import com.pavis.backend.slim.framework.security.context.AuthenticationContextHolder;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import com.pavis.backend.slim.project.system.service.SysUserService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 登录校验方法
+ *
+ * @author semi
+ */
+@Component
+public class LoginService {
+    @Autowired
+    private TokenService tokenService;
+
+    @Resource
+    private AuthenticationManager authenticationManager;
+
+    @Autowired
+    private SysUserService userService;
+
+    /**
+     * 登录验证
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @return 结果
+     */
+    public String login(String username, String password) {
+        // 登录前置校验
+        loginPreCheck(username, password);
+        // 用户验证
+        Authentication authentication = null;
+        try {
+            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
+            AuthenticationContextHolder.setContext(authenticationToken);
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager.authenticate(authenticationToken);
+        } catch (Exception e) {
+            if (e instanceof BadCredentialsException) {
+                throw new UserPasswordNotMatchException();
+            } else {
+                throw new ServiceException(e.getMessage());
+            }
+        } finally {
+            AuthenticationContextHolder.clearContext();
+        }
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        recordLoginInfo(loginUser.getUserId());
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
+
+    /**
+     * 登录前置校验
+     *
+     * @param username 用户名
+     * @param password 用户密码
+     */
+    public void loginPreCheck(String username, String password) {
+        // 用户名或密码为空 错误
+        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
+            throw new UserNotExistsException();
+        }
+        // 密码如果不在指定范围内 错误
+        if (password.length() < Constant.PASSWORD_MIN_LENGTH
+                || password.length() > Constant.PASSWORD_MAX_LENGTH) {
+            throw new UserPasswordNotMatchException();
+        }
+        // 用户名不在指定范围内 错误
+        if (username.length() < Constant.USERNAME_MIN_LENGTH
+                || username.length() > Constant.USERNAME_MAX_LENGTH) {
+            throw new UserPasswordNotMatchException();
+        }
+    }
+
+    /**
+     * 记录登录信息
+     *
+     * @param userId 用户ID
+     */
+    public void recordLoginInfo(Long userId) {
+        SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
+        sysUser.setLastLoginIp(ServletUtils.getIpAddr());
+        sysUser.setLastLoginDate(DateUtil.date());
+        userService.updateById(sysUser);
+    }
+}

+ 73 - 0
src/main/java/com/pavis/backend/slim/framework/security/service/PasswordService.java

@@ -0,0 +1,73 @@
+package com.pavis.backend.slim.framework.security.service;
+
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.common.exception.user.UserPasswordNotMatchException;
+import com.pavis.backend.slim.common.exception.user.UserPasswordRetryLimitExceedException;
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.framework.redis.RedisCache;
+import com.pavis.backend.slim.framework.security.context.AuthenticationContextHolder;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 密码验证处理
+ *
+ * @author semi
+ */
+@Component
+public class PasswordService {
+    @Autowired
+    private RedisCache redisCache;
+
+    @Value(value = "${user.password.maxRetryCount}")
+    private int maxRetryCount;
+
+    @Value(value = "${user.password.lockTime}")
+    private int lockTime;
+
+    /**
+     * 登录账户密码错误次数缓存键名
+     *
+     * @param username 用户名
+     * @return 缓存键key
+     */
+    private String getCacheKey(String username) {
+        return Constant.PWD_ERR_CNT_KEY + username;
+    }
+
+    public void validate(SysUser user) {
+        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
+        String username = usernamePasswordAuthenticationToken.getName();
+        String password = usernamePasswordAuthenticationToken.getCredentials().toString();
+        Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
+        if (retryCount == null) {
+            retryCount = 0;
+        }
+        if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) {
+            throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
+        }
+
+        if (!matches(user, password)) {
+            retryCount = retryCount + 1;
+            redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
+            throw new UserPasswordNotMatchException();
+        } else {
+            clearLoginRecordCache(username);
+        }
+    }
+
+    public boolean matches(SysUser user, String rawPassword) {
+        return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
+    }
+
+    public void clearLoginRecordCache(String loginName) {
+        if (redisCache.hasKey(getCacheKey(loginName))) {
+            redisCache.deleteObject(getCacheKey(loginName));
+        }
+    }
+}

+ 151 - 0
src/main/java/com/pavis/backend/slim/framework/security/service/PermissionService.java

@@ -0,0 +1,151 @@
+package com.pavis.backend.slim.framework.security.service;
+
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import com.pavis.backend.slim.framework.security.context.PermissionContextHolder;
+import com.pavis.backend.slim.project.system.domain.SysRole;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Set;
+
+/**
+ * 自定义权限实现,ss取自SpringSecurity首字母
+ *
+ * @author semi
+ */
+@Service("ss")
+public class PermissionService {
+    /**
+     * 所有权限标识
+     */
+    private static final String ALL_PERMISSION = "*:*:*";
+
+    /**
+     * 管理员角色权限标识
+     */
+    private static final String SUPER_ADMIN = "admin";
+
+    private static final String ROLE_DELIMETER = ",";
+
+    private static final String PERMISSION_DELIMETER = ",";
+
+    /**
+     * 验证用户是否具备某权限
+     *
+     * @param permission 权限字符串
+     * @return 用户是否具备某权限
+     */
+    public boolean hasPermi(String permission) {
+        if (StringUtils.isEmpty(permission)) {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
+            return false;
+        }
+        PermissionContextHolder.setContext(permission);
+        return hasPermissions(loginUser.getPermissions(), permission);
+    }
+
+    /**
+     * 验证用户是否不具备某权限,与 hasPermi逻辑相反
+     *
+     * @param permission 权限字符串
+     * @return 用户是否不具备某权限
+     */
+    public boolean lacksPermi(String permission) {
+        return hasPermi(permission) != true;
+    }
+
+    /**
+     * 验证用户是否具有以下任意一个权限
+     *
+     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
+     * @return 用户是否具有以下任意一个权限
+     */
+    public boolean hasAnyPermi(String permissions) {
+        if (StringUtils.isEmpty(permissions)) {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
+            return false;
+        }
+        PermissionContextHolder.setContext(permissions);
+        Set<String> authorities = loginUser.getPermissions();
+        for (String permission : permissions.split(PERMISSION_DELIMETER)) {
+            if (permission != null && hasPermissions(authorities, permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断用户是否拥有某个角色
+     *
+     * @param role 角色字符串
+     * @return 用户是否具备某角色
+     */
+    public boolean hasRole(String role) {
+        if (StringUtils.isEmpty(role)) {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
+            return false;
+        }
+        for (SysRole sysRole : loginUser.getUser().getRoles()) {
+            String roleKey = sysRole.getRoleKey();
+            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 验证用户是否不具备某角色,与 isRole逻辑相反。
+     *
+     * @param role 角色名称
+     * @return 用户是否不具备某角色
+     */
+    public boolean lacksRole(String role) {
+        return hasRole(role) != true;
+    }
+
+    /**
+     * 验证用户是否具有以下任意一个角色
+     *
+     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
+     * @return 用户是否具有以下任意一个角色
+     */
+    public boolean hasAnyRoles(String roles) {
+        if (StringUtils.isEmpty(roles)) {
+            return false;
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
+            return false;
+        }
+        for (String role : roles.split(ROLE_DELIMETER)) {
+            if (hasRole(role)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否包含权限
+     *
+     * @param permissions 权限列表
+     * @param permission  权限字符串
+     * @return 用户是否具备某权限
+     */
+    private boolean hasPermissions(Set<String> permissions, String permission) {
+        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
+    }
+}

+ 70 - 0
src/main/java/com/pavis/backend/slim/framework/security/service/SysPermissionService.java

@@ -0,0 +1,70 @@
+package com.pavis.backend.slim.framework.security.service;
+
+import com.pavis.backend.slim.project.system.domain.SysRole;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import com.pavis.backend.slim.project.system.service.SysMenuService;
+import com.pavis.backend.slim.project.system.service.SysRoleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author semi
+ */
+@Component
+public class SysPermissionService {
+    @Autowired
+    private SysRoleService roleService;
+
+    @Autowired
+    private SysMenuService menuService;
+
+    /**
+     * 获取角色数据权限
+     *
+     * @param user 用户信息
+     * @return 角色权限信息
+     */
+    public Set<String> getRolePermission(SysUser user) {
+        Set<String> roles = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin()) {
+            roles.add("admin");
+        } else {
+            roles.addAll(roleService.selectRolePermsByUserId(user.getUserId()));
+        }
+        return roles;
+    }
+
+    /**
+     * 获取菜单数据权限
+     *
+     * @param user 用户信息
+     * @return 菜单权限信息
+     */
+    public Set<String> getMenuPermission(SysUser user) {
+        Set<String> perms = new HashSet<String>();
+        // 管理员拥有所有权限
+        if (user.isAdmin()) {
+            perms.add("*:*:*");
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (!roles.isEmpty()) {
+                // 多角色设置permissions属性,以便数据权限匹配权限
+                for (SysRole role : roles) {
+                    Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
+                    role.setPermissions(rolePerms);
+                    perms.addAll(rolePerms);
+                }
+            } else {
+                perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
+            }
+        }
+        return perms;
+    }
+}

+ 191 - 0
src/main/java/com/pavis/backend/slim/framework/security/service/TokenService.java

@@ -0,0 +1,191 @@
+package com.pavis.backend.slim.framework.security.service;
+
+import cn.hutool.core.lang.UUID;
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.framework.redis.RedisCache;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * token验证处理
+ *
+ * @author semi
+ */
+@Component
+public class TokenService {
+    /**
+     * 令牌自定义标识
+     */
+    @Value("${token.header}")
+    private String header;
+
+    /**
+     * 令牌秘钥
+     */
+    @Value("${token.secret}")
+    private String secret;
+
+    /**
+     * 令牌有效期(默认30分钟)
+     */
+    @Value("${token.expireTime}")
+    private int expireTime;
+
+    protected static final long MILLIS_SECOND = 1000;
+
+    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
+
+    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+    public LoginUser getLoginUser(HttpServletRequest request) {
+        // 获取请求携带的令牌
+        String token = getToken(request);
+        if (StringUtils.isNotEmpty(token)) {
+            try {
+                Claims claims = parseToken(token);
+                // 解析对应的权限以及用户信息
+                String uuid = (String) claims.get(Constant.LOGIN_USER_KEY);
+                String userKey = getTokenKey(uuid);
+                LoginUser user = redisCache.getCacheObject(userKey);
+                return user;
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 设置用户身份信息
+     */
+    public void setLoginUser(LoginUser loginUser) {
+        if (loginUser != null && StringUtils.isNotEmpty(loginUser.getToken())) {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 删除用户身份信息
+     */
+    public void delLoginUser(String token) {
+        if (StringUtils.isNotEmpty(token)) {
+            String userKey = getTokenKey(token);
+            redisCache.deleteObject(userKey);
+        }
+    }
+
+    /**
+     * 创建令牌
+     *
+     * @param loginUser 用户信息
+     * @return 令牌
+     */
+    public String createToken(LoginUser loginUser) {
+        String token = UUID.fastUUID().toString();
+        loginUser.setToken(token);
+        refreshToken(loginUser);
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(Constant.LOGIN_USER_KEY, token);
+        return createToken(claims);
+    }
+
+    /**
+     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
+     *
+     * @param loginUser 用户信息
+     */
+    public void verifyToken(LoginUser loginUser) {
+        long expireTime = loginUser.getExpireTime();
+        long currentTime = System.currentTimeMillis();
+        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
+            refreshToken(loginUser);
+        }
+    }
+
+    /**
+     * 刷新令牌有效期
+     *
+     * @param loginUser 登录信息
+     */
+    public void refreshToken(LoginUser loginUser) {
+        loginUser.setLoginTime(System.currentTimeMillis());
+        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
+        // 根据uuid将loginUser缓存
+        String userKey = getTokenKey(loginUser.getToken());
+        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
+    }
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String createToken(Map<String, Object> claims) {
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+        return token;
+    }
+
+    /**
+     * 从令牌中获取数据声明
+     *
+     * @param token 令牌
+     * @return 数据声明
+     */
+    private Claims parseToken(String token) {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    public String getUsernameFromToken(String token) {
+        Claims claims = parseToken(token);
+        return claims.getSubject();
+    }
+
+    /**
+     * 获取请求token
+     *
+     * @param request
+     * @return token
+     */
+    private String getToken(HttpServletRequest request) {
+        String token = request.getHeader(header);
+        if (StringUtils.isNotEmpty(token) && token.startsWith(Constant.TOKEN_PREFIX)) {
+            token = token.replace(Constant.TOKEN_PREFIX, "");
+        }
+        return token;
+    }
+
+    private String getTokenKey(String uuid) {
+        return Constant.LOGIN_TOKEN_KEY + uuid;
+    }
+}

+ 52 - 0
src/main/java/com/pavis/backend/slim/framework/security/service/UserDetailsServiceImpl.java

@@ -0,0 +1,52 @@
+package com.pavis.backend.slim.framework.security.service;
+
+import com.pavis.backend.slim.common.exception.ServiceException;
+import com.pavis.backend.slim.framework.security.LoginUser;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import com.pavis.backend.slim.project.system.service.SysUserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户验证处理
+ *
+ * @author semi
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
+
+    @Autowired
+    private SysUserService userService;
+
+    @Autowired
+    private PasswordService passwordService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) {
+        SysUser user = userService.selectByUserName(username);
+        if (user == null) {
+            log.info("登录用户:{} 不存在.", username);
+            throw new ServiceException("登录用户:" + username + " 不存在");
+        } else if (user.isDeleted()) {
+            log.info("登录用户:{} 已被删除.", username);
+            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
+        } else if (!user.isAvailable()) {
+            log.info("登录用户:{} 已被停用.", username);
+            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
+        }
+        passwordService.validate(user);
+        return createLoginUser(user);
+    }
+
+    public UserDetails createLoginUser(SysUser user) {
+        return new LoginUser(user.getUserId(), user, permissionService.getMenuPermission(user));
+    }
+}

+ 194 - 0
src/main/java/com/pavis/backend/slim/framework/web/domain/AjaxResult.java

@@ -0,0 +1,194 @@
+package com.pavis.backend.slim.framework.web.domain;
+
+import com.pavis.backend.slim.common.constant.HttpStatus;
+
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * 操作消息提醒
+ *
+ * @author semi
+ */
+public class AjaxResult extends HashMap<String, Object> {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 状态码
+     */
+    public static final String CODE_TAG = "code";
+
+    /**
+     * 返回内容
+     */
+    public static final String MSG_TAG = "msg";
+
+    /**
+     * 数据对象
+     */
+    public static final String DATA_TAG = "data";
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
+     */
+    public AjaxResult() {
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     */
+    public AjaxResult(int code, String msg) {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     * @param data 数据对象
+     */
+    public AjaxResult(int code, String msg, Object data) {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+        if (data != null) {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @return 成功消息
+     */
+    public static AjaxResult success() {
+        return AjaxResult.success("操作成功");
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static AjaxResult success(Object data) {
+        return AjaxResult.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static AjaxResult success(String msg) {
+        return AjaxResult.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static AjaxResult success(String msg, Object data) {
+        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult warn(String msg) {
+        return AjaxResult.warn(msg, null);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static AjaxResult warn(String msg, Object data) {
+        return new AjaxResult(HttpStatus.WARN, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @return 错误消息
+     */
+    public static AjaxResult error() {
+        return AjaxResult.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @return 错误消息
+     */
+    public static AjaxResult error(String msg) {
+        return AjaxResult.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg  返回内容
+     * @param data 数据对象
+     * @return 错误消息
+     */
+    public static AjaxResult error(String msg, Object data) {
+        return new AjaxResult(HttpStatus.ERROR, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param code 状态码
+     * @param msg  返回内容
+     * @return 错误消息
+     */
+    public static AjaxResult error(int code, String msg) {
+        return new AjaxResult(code, msg, null);
+    }
+
+    /**
+     * 是否为成功消息
+     *
+     * @return 结果
+     */
+    public boolean isSuccess() {
+        return !isError();
+    }
+
+    /**
+     * 是否为错误消息
+     *
+     * @return 结果
+     */
+    public boolean isError() {
+        return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
+    }
+
+    /**
+     * 方便链式调用
+     *
+     * @param key   键
+     * @param value 值
+     * @return 数据对象
+     */
+    @Override
+    public AjaxResult put(String key, Object value) {
+        super.put(key, value);
+        return this;
+    }
+}

+ 121 - 0
src/main/java/com/pavis/backend/slim/framework/web/domain/BaseEntity.java

@@ -0,0 +1,121 @@
+package com.pavis.backend.slim.framework.web.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity基类
+ *
+ * @author semi
+ */
+public class BaseEntity implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 搜索值
+     */
+    @TableField(select = false, updateStrategy = FieldStrategy.NEVER)
+    @JsonIgnore
+    private String searchValue;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private Map<String, Object> params;
+
+    public String getSearchValue() {
+        return searchValue;
+    }
+
+    public void setSearchValue(String searchValue) {
+        this.searchValue = searchValue;
+    }
+
+    public String getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy) {
+        this.createBy = createBy;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Map<String, Object> getParams() {
+        if (params == null) {
+            params = new HashMap<>();
+        }
+        return params;
+    }
+
+    public void setParams(Map<String, Object> params) {
+        this.params = params;
+    }
+}

+ 96 - 0
src/main/java/com/pavis/backend/slim/framework/web/exception/GlobalExceptionHandler.java

@@ -0,0 +1,96 @@
+package com.pavis.backend.slim.framework.web.exception;
+
+import com.pavis.backend.slim.common.constant.HttpStatus;
+import com.pavis.backend.slim.common.exception.ServiceException;
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 全局异常处理器
+ *
+ * @author semi
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+    /**
+     * 权限校验异常
+     */
+    @ExceptionHandler(AccessDeniedException.class)
+    public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
+        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
+    }
+
+    /**
+     * 请求方式不支持
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
+                                                          HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(ServiceException.class)
+    public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) {
+        log.error(e.getMessage(), e);
+        Integer code = e.getCode();
+        return code != null ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 拦截未知的运行时异常
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生未知异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 系统异常
+     */
+    @ExceptionHandler(Exception.class)
+    public AjaxResult handleException(Exception e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生系统异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(BindException.class)
+    public AjaxResult handleBindException(BindException e) {
+        log.error(e.getMessage(), e);
+        String message = e.getAllErrors().get(0).getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 自定义验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        log.error(e.getMessage(), e);
+        String message = e.getBindingResult().getFieldError().getDefaultMessage();
+        return AjaxResult.error(message);
+    }
+}

+ 65 - 0
src/main/java/com/pavis/backend/slim/project/system/controller/SysFileController.java

@@ -0,0 +1,65 @@
+package com.pavis.backend.slim.project.system.controller;
+
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import com.pavis.backend.slim.project.system.service.SysFileService;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author semi
+ * @create 2023-04-24 14:09
+ */
+@RestController
+public class SysFileController {
+
+    @Autowired
+    private SysFileService fileService;
+
+
+    @ApiOperation("单个文件上传")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "file", value = "需要上传的文件", required = true, paramType = "form")
+    })
+    @PostMapping(value = "/upload/file")
+    public AjaxResult uploadFile(MultipartFile file) throws Exception {
+        return AjaxResult.success("文件上传成功!", fileService.uploadFile(file));
+    }
+
+    @ApiOperation("多个文件上传")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "files", value = "文件列表", required = true, paramType = "form")
+    })
+    @PostMapping(value = "/upload/files")
+    public AjaxResult uploadFiles(@RequestParam("files") MultipartFile[] files) throws Exception {
+        return AjaxResult.success("文件上传成功!", fileService.uploadFiles(files));
+    }
+
+    @ApiOperation("获取文件列表")
+    @GetMapping(value = "/file/list")
+    public AjaxResult listFiles() throws Exception {
+        return AjaxResult.success("文件获取成功!", fileService.listFiles());
+    }
+
+    @ApiOperation("获取文件树形列表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "知识库id", required = true, paramType = "path")
+    })
+    @GetMapping(value = "/file/tree/{id}")
+    public AjaxResult listTreeFiles(@PathVariable("id") String kbId) throws Exception {
+        return AjaxResult.success("文件获取成功!", fileService.listTreeFiles(kbId));
+    }
+
+    @ApiOperation("预览文件")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "key", value = "文件key值", required = true, paramType = "query")
+    })
+    @GetMapping(value = "/preview")
+    public AjaxResult preview(String key) throws Exception {
+        return AjaxResult.success("预览地址获取成功!", fileService.preview(key));
+    }
+
+}

+ 112 - 0
src/main/java/com/pavis/backend/slim/project/system/controller/SysKbController.java

@@ -0,0 +1,112 @@
+package com.pavis.backend.slim.project.system.controller;
+
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import com.pavis.backend.slim.project.system.domain.SysFile;
+import com.pavis.backend.slim.project.system.domain.SysKb;
+import com.pavis.backend.slim.project.system.service.SysFileService;
+import com.pavis.backend.slim.project.system.service.SysKbFileService;
+import com.pavis.backend.slim.project.system.service.SysKbService;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author semi
+ * @create 2023-05-08 17:12
+ */
+@RestController
+@RequestMapping("/kb")
+public class SysKbController {
+
+    @Autowired
+    private SysKbService kbService;
+    @Autowired
+    private SysFileService fileService;
+
+    @Autowired
+    private SysKbFileService kbFileService;
+
+    @ApiOperation("创建知识库")
+    @PostMapping("/create")
+    public AjaxResult createKb(@Validated @RequestBody SysKb kb) {
+        kb.setUserId(SecurityUtils.getUserId());
+        if (kbService.checkKbNameExist(kb)) {
+            return AjaxResult.error("创建知识库:" + kb.getName() + "失败,知识库已存在!");
+        }
+        return AjaxResult.success("知识库创建成功!", kbService.create(kb));
+    }
+
+    @ApiOperation("获取知识库")
+    @GetMapping("/list")
+    public AjaxResult listKb() {
+        return AjaxResult.success("知识库获取成功!", kbService.selectByUser(SecurityUtils.getUserId()));
+    }
+
+    @ApiOperation("为知识库添加文档")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "kbId", value = "知识库id", required = true, paramType = "form"),
+            @ApiImplicitParam(name = "file", value = "要添加的知识库文档", paramType = "form"),
+            @ApiImplicitParam(name = "icon", value = "文件图标", paramType = "form"),
+            @ApiImplicitParam(name = "path", value = "文件所属文件夹,例如:/文件夹1/文件夹2/,如不填写默认保存在根目录,必须以/开头和结尾", paramType = "form"),
+            @ApiImplicitParam(name = "isDir", value = "添加的文档是否是文件夹,如果是文件夹则会创建path参数的最后一个文件夹", required = true, paramType = "form")
+    })
+    @PostMapping("/doc/add")
+    public AjaxResult addKbDoc(String kbId, MultipartFile file, String icon, String path, Boolean isDir) {
+        SysKb sysKb = kbService.getById(kbId);
+        if (sysKb == null) {
+            return AjaxResult.error("知识库不存在!");
+        }
+        return AjaxResult.success("知识库文档添加成功!", kbFileService.addDoc(kbId, file, icon, path, isDir));
+    }
+
+    @ApiOperation("预览知识库文档")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "fileId", value = "要预览的文件id", required = true, paramType = "form")
+    })
+    @PostMapping("/doc/preview/{fileId}")
+    public AjaxResult previewKbDoc(@PathVariable("fileId") String fileId) {
+        SysFile sysFile = fileService.getById(fileId);
+        if (sysFile == null) {
+            return AjaxResult.error("文档不存在!");
+        }
+        return AjaxResult.success("文档加载成功!", sysFile);
+    }
+
+    @ApiOperation("删除知识库文档")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "kbId", value = "知识库id", required = true, paramType = "form"),
+            @ApiImplicitParam(name = "fileId", value = "文件id", required = true, paramType = "form")
+    })
+    @PostMapping("/doc/remove")
+    public AjaxResult removeKbDoc(String kbId, String fileId) {
+        kbFileService.delDoc(kbId, fileId);
+        return AjaxResult.success("知识库文档删除成功!");
+    }
+
+    @ApiOperation("获取知识库信息")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "知识库id", required = true, paramType = "path")
+    })
+    @GetMapping("/{id}")
+    public AjaxResult getKbInfo(@PathVariable("id") String kbId) {
+        SysKb kb = kbService.getById(kbId);
+        if (kb == null) {
+            return AjaxResult.error("知识库不存在!");
+        }
+        return AjaxResult.success("知识库信息获取成功!", kb);
+    }
+
+    @ApiOperation("删除知识库")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "知识库id", required = true, paramType = "path")
+    })
+    @GetMapping("/del/{id}")
+    public AjaxResult delKb(@PathVariable("id") String kbId) {
+        return AjaxResult.success("知识库删除成功!", kbService.removeById(kbId));
+    }
+}

+ 17 - 0
src/main/java/com/pavis/backend/slim/project/system/controller/SysKgController.java

@@ -0,0 +1,17 @@
+package com.pavis.backend.slim.project.system.controller;
+
+import com.pavis.backend.slim.project.system.service.SysKgService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author semi
+ * @create 2023-06-01 11:32
+ */
+@RestController
+@RequestMapping("/kg")
+public class SysKgController {
+    @Autowired
+    private SysKgService kgService;
+}

+ 67 - 0
src/main/java/com/pavis/backend/slim/project/system/controller/SysLoginController.java

@@ -0,0 +1,67 @@
+package com.pavis.backend.slim.project.system.controller;
+
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.framework.security.LoginBody;
+import com.pavis.backend.slim.framework.security.service.LoginService;
+import com.pavis.backend.slim.framework.security.service.SysPermissionService;
+import com.pavis.backend.slim.framework.web.domain.AjaxResult;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Set;
+
+/**
+ * 登录验证
+ *
+ * @author semi
+ */
+@RestController
+public class SysLoginController {
+    @Autowired
+    private LoginService loginService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    /**
+     * 登录方法
+     *
+     * @param loginBody 登录信息
+     * @return 结果
+     */
+    @ApiOperation("登录")
+    @PostMapping("/login")
+    public AjaxResult login(@RequestBody LoginBody loginBody) {
+        AjaxResult ajax = AjaxResult.success();
+        // 生成令牌
+        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword());
+        ajax.put(Constant.TOKEN, token);
+        return ajax;
+    }
+
+    /**
+     * 获取用户信息
+     *
+     * @return 用户信息
+     */
+    @ApiOperation("获取用户信息")
+    @GetMapping("getInfo")
+    public AjaxResult getInfo() {
+        SysUser user = SecurityUtils.getLoginUser().getUser();
+        // 角色集合
+        Set<String> roles = permissionService.getRolePermission(user);
+        // 权限集合
+        Set<String> permissions = permissionService.getMenuPermission(user);
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("user", user);
+        ajax.put("roles", roles);
+        ajax.put("permissions", permissions);
+        return ajax;
+    }
+}

+ 118 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysEntity.java

@@ -0,0 +1,118 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+
+/**
+ * 图谱实体表
+ *
+ * @author semi
+ * @create 2023-06-01 10:45
+ */
+public class SysEntity extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+    /**
+     * 实体ID
+     */
+    private String entityId;
+    /**
+     * 图谱ID
+     */
+    private String kgId;
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    /**
+     * 实体名称
+     */
+    private String name;
+    /**
+     * 实体别称
+     */
+    private String nickName;
+    /**
+     * 实体简介
+     */
+    private String profile;
+    /**
+     * 实体类型
+     */
+    private String type;
+
+    // 以下为getter和setter方法
+    public String getEntityId() {
+        return entityId;
+    }
+
+    public void setEntityId(String entityId) {
+        this.entityId = entityId;
+    }
+
+    public String getKgId() {
+        return kgId;
+    }
+
+    public void setKgId(String kgId) {
+        this.kgId = kgId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public String getProfile() {
+        return profile;
+    }
+
+    public void setProfile(String profile) {
+        this.profile = profile;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+
+    @Override
+    public String toString() {
+        return "SysEntity{" +
+                "entityId='" + getEntityId() + '\'' +
+                ", kgId='" + getKgId() + '\'' +
+                ", userId=" + getUserId() +
+                ", name='" + getName() + '\'' +
+                ", nickName='" + getNickName() + '\'' +
+                ", profile='" + getProfile() + '\'' +
+                ", type='" + getType() + '\'' +
+                ", createBy='" + getCreateBy() + '\'' +
+                ", createTime=" + getCreateTime() +
+                ", updateBy='" + getUpdateBy() + '\'' +
+                ", updateTime=" + getUpdateTime() +
+                ", remark='" + getRemark() + '\'' +
+                '}';
+    }
+}

+ 100 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysEntityInstance.java

@@ -0,0 +1,100 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+
+/**
+ * 实体实例
+ *
+ * @author semi
+ * @create 2023-06-01 10:36
+ */
+public class SysEntityInstance extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 实例id
+     */
+    @TableId
+    private String instanceId;
+
+    /**
+     * 实体id
+     */
+    private String entityId;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 实例名称
+     */
+    private String name;
+
+    /**
+     * 实例别称,多个别称使用逗号隔开
+     */
+    private String nickName;
+
+    public SysEntityInstance() {
+    }
+
+    public String getInstanceId() {
+        return instanceId;
+    }
+
+    public void setInstanceId(String instanceId) {
+        this.instanceId = instanceId;
+    }
+
+    public String getEntityId() {
+        return entityId;
+    }
+
+    public void setEntityId(String entityId) {
+        this.entityId = entityId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    @Override
+    public String toString() {
+        return "EntityInstance{" +
+                "instanceId='" + getInstanceId() + '\'' +
+                ", entityId='" + getEntityId() + '\'' +
+                ", userId=" + getUserId() +
+                ", name='" + getName() + '\'' +
+                ", nickName='" + getNickName() + '\'' +
+                ", createBy='" + getCreateBy() + '\'' +
+                ", createTime=" + getCreateTime() +
+                ", updateBy='" + getUpdateBy() + '\'' +
+                ", updateTime=" + getUpdateBy() +
+                ", remark='" + getRemark() + '\'' +
+                '}';
+    }
+}

+ 192 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysFile.java

@@ -0,0 +1,192 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+
+/**
+ * 文件基类
+ *
+ * @author semi
+ * @create 2023-04-23 13:25
+ */
+public class SysFile extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 文件id
+     */
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String fileId;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 文件名称
+     */
+    private String name;
+
+    /**
+     * 文件原名称
+     */
+    private String originalName;
+
+    /**
+     * 文件在oss中的key值
+     */
+    private String objectKey;
+
+    /**
+     * 文件url地址
+     */
+    private String url;
+
+    /**
+     * 文件所属路径
+     */
+    private String path;
+
+    /**
+     * 文件大小
+     */
+    private Long size;
+
+    /**
+     * 文件类型
+     * 0. 其他
+     * 1. 文档
+     * 2. 图片
+     * 3. 音频
+     * 4. 视频
+     */
+    private Integer type;
+
+    /**
+     * 文件后缀
+     */
+    private String suffix;
+
+    /**
+     * 文件图标
+     */
+    private String icon;
+
+    /**
+     * 文件md5唯一标识符
+     */
+    private String identifier;
+
+    /**
+     * 是否是文件夹
+     * 1 是文件夹
+     * 0 不是文件夹
+     */
+    private Boolean isDir;
+
+    public String getFileId() {
+        return fileId;
+    }
+
+    public void setFileId(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getOriginalName() {
+        return originalName;
+    }
+
+    public void setOriginalName(String originalName) {
+        this.originalName = originalName;
+    }
+
+    public String getObjectKey() {
+        return objectKey;
+    }
+
+    public void setObjectKey(String objectKey) {
+        this.objectKey = objectKey;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public Long getSize() {
+        return size;
+    }
+
+    public void setSize(Long size) {
+        this.size = size;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    public void setSuffix(String suffix) {
+        this.suffix = suffix;
+    }
+
+    public String getIcon() {
+        return icon;
+    }
+
+    public void setIcon(String icon) {
+        this.icon = icon;
+    }
+
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    public void setIdentifier(String identifier) {
+        this.identifier = identifier;
+    }
+
+    public Boolean getDir() {
+        return isDir;
+    }
+
+    public void setDir(Boolean dir) {
+        isDir = dir;
+    }
+}

+ 118 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysKb.java

@@ -0,0 +1,118 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 知识库
+ *
+ * @author semi
+ * @create 2023-05-08 17:05
+ */
+public class SysKb extends BaseEntity {
+
+    /**
+     * 知识库id
+     */
+    @ApiModelProperty("知识库id")
+    @TableId(type = IdType.ASSIGN_UUID)
+    private String kbId;
+
+    /**
+     * 知识库所属用户id
+     */
+    @ApiModelProperty("知识库所属用户id")
+    private Long userId;
+
+    /**
+     * 知识库名称
+     */
+    @ApiModelProperty(value = "知识库名称,必填",required = true)
+    private String name;
+
+    /**
+     * 知识库简介
+     */
+    @ApiModelProperty("知识库简介")
+    private String profile;
+
+    /**
+     * 知识库链接
+     */
+    @ApiModelProperty("知识库分享地址")
+    private String url;
+
+    /**
+     * 知识库关键字
+     * 主要用于检索
+     */
+    @ApiModelProperty("知识库关键字,主要用于检索,多个关键字使用,分隔符分割")
+    private String keywords;
+
+    /**
+     * 知识库封面
+     */
+    @ApiModelProperty("知识库封面图片地址,需要先调用单个文件上传拿到文件地址")
+    private String cover;
+
+    public String getKbId() {
+        return kbId;
+    }
+
+    public void setKbId(String kbId) {
+        this.kbId = kbId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    @NotBlank(message = "知识库名称不能为空")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getProfile() {
+        return profile;
+    }
+
+    public void setProfile(String profile) {
+        this.profile = profile;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getKeywords() {
+        return keywords;
+    }
+
+    public void setKeywords(String keywords) {
+        this.keywords = keywords;
+    }
+
+    public String getCover() {
+        return cover;
+    }
+
+    public void setCover(String cover) {
+        this.cover = cover;
+    }
+}

+ 42 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysKbFile.java

@@ -0,0 +1,42 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+
+/**
+ * 知识库文档关联表
+ *
+ * @author semi
+ * @create 2023-05-09 11:07
+ */
+public class SysKbFile extends BaseEntity {
+    private String kbId;
+
+    private String fileId;
+
+    private Long userId;
+
+
+    public String getKbId() {
+        return kbId;
+    }
+
+    public void setKbId(String kbId) {
+        this.kbId = kbId;
+    }
+
+    public String getFileId() {
+        return fileId;
+    }
+
+    public void setFileId(String fileId) {
+        this.fileId = fileId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+}

+ 104 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysKg.java

@@ -0,0 +1,104 @@
+package com.pavis.backend.slim.project.system.domain;
+
+
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+
+/**
+ * 图谱表
+ *
+ * @author semi
+ * @create 2023-06-01 10:50
+ */
+public class SysKg extends BaseEntity {
+    /**
+     * 图谱ID
+     */
+    private String kgId;
+    /**
+     * 知识库ID
+     */
+    private String kbId;
+    /**
+     * 文档ID列表,全选时为1,没选时为0,选择部分时为文件id列表,用逗号隔开
+     */
+    private String fileIds;
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    /**
+     * 图谱名称
+     */
+    private String name;
+    /**
+     * 图谱简介
+     */
+    private String profile;
+
+
+    // 以下为getter和setter方法
+    public String getKgId() {
+        return kgId;
+    }
+
+    public void setKgId(String kgId) {
+        this.kgId = kgId;
+    }
+
+    public String getKbId() {
+        return kbId;
+    }
+
+    public void setKbId(String kbId) {
+        this.kbId = kbId;
+    }
+
+    public String getFileIds() {
+        return fileIds;
+    }
+
+    public void setFileIds(String fileIds) {
+        this.fileIds = fileIds;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getProfile() {
+        return profile;
+    }
+
+    public void setProfile(String profile) {
+        this.profile = profile;
+    }
+
+    @Override
+    public String toString() {
+        return "SysKg{" +
+                "kgId='" + getKgId() + '\'' +
+                ", kbId='" + getKbId() + '\'' +
+                ", fileIds='" + getFileIds() + '\'' +
+                ", userId=" + getUserId() +
+                ", name='" + getName() + '\'' +
+                ", profile='" + getProfile() + '\'' +
+                ", createBy='" + getCreateBy() + '\'' +
+                ", createTime=" + getCreateTime() +
+                ", updateBy='" + getUpdateBy() + '\'' +
+                ", updateTime=" + getUpdateTime() +
+                ", remark='" + getRemark() + '\'' +
+                '}';
+    }
+}

+ 261 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysMenu.java

@@ -0,0 +1,261 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 菜单权限表 sys_menu
+ *
+ * @author semi
+ */
+public class SysMenu extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 菜单ID
+     */
+    @TableId
+    private Long menuId;
+
+    /**
+     * 菜单名称
+     */
+    private String menuName;
+
+    /**
+     * 父菜单ID
+     */
+    private Long parentId;
+
+    /**
+     * 父菜单名称
+     */
+    private String parentName;
+
+    /**
+     * 显示顺序
+     */
+    private Integer orderNum;
+
+    /**
+     * 路由地址
+     */
+    private String path;
+
+    /**
+     * 组件路径
+     */
+    private String component;
+
+    /**
+     * 路由参数
+     */
+    private String query;
+
+    /**
+     * 是否为外链(1是 0否)
+     */
+    private String isFrame;
+
+    /**
+     * 是否缓存(1缓存 0不缓存)
+     */
+    private String isCache;
+
+    /**
+     * 类型(F目录 M菜单 B按钮)
+     */
+    private String menuType;
+
+    /**
+     * 显示状态(1显示 0隐藏)
+     */
+    private String visible;
+
+    /**
+     * 菜单状态(1正常 0停用)
+     */
+    private String status;
+
+    /**
+     * 权限字符串
+     */
+    private String perms;
+
+    /**
+     * 菜单图标
+     */
+    private String icon;
+
+    /**
+     * 子菜单
+     */
+    private List<SysMenu> children = new ArrayList<SysMenu>();
+
+    public Long getMenuId() {
+        return menuId;
+    }
+
+    public void setMenuId(Long menuId) {
+        this.menuId = menuId;
+    }
+
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符")
+    public String getMenuName() {
+        return menuName;
+    }
+
+    public void setMenuName(String menuName) {
+        this.menuName = menuName;
+    }
+
+    public String getParentName() {
+        return parentName;
+    }
+
+    public void setParentName(String parentName) {
+        this.parentName = parentName;
+    }
+
+    public Long getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(Long parentId) {
+        this.parentId = parentId;
+    }
+
+    @NotNull(message = "显示顺序不能为空")
+    public Integer getOrderNum() {
+        return orderNum;
+    }
+
+    public void setOrderNum(Integer orderNum) {
+        this.orderNum = orderNum;
+    }
+
+    @Size(min = 0, max = 200, message = "路由地址不能超过200个字符")
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    @Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
+    public String getComponent() {
+        return component;
+    }
+
+    public void setComponent(String component) {
+        this.component = component;
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    public void setQuery(String query) {
+        this.query = query;
+    }
+
+    public String getIsFrame() {
+        return isFrame;
+    }
+
+    public void setIsFrame(String isFrame) {
+        this.isFrame = isFrame;
+    }
+
+    public String getIsCache() {
+        return isCache;
+    }
+
+    public void setIsCache(String isCache) {
+        this.isCache = isCache;
+    }
+
+    @NotBlank(message = "菜单类型不能为空")
+    public String getMenuType() {
+        return menuType;
+    }
+
+    public void setMenuType(String menuType) {
+        this.menuType = menuType;
+    }
+
+    public String getVisible() {
+        return visible;
+    }
+
+    public void setVisible(String visible) {
+        this.visible = visible;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符")
+    public String getPerms() {
+        return perms;
+    }
+
+    public void setPerms(String perms) {
+        this.perms = perms;
+    }
+
+    public String getIcon() {
+        return icon;
+    }
+
+    public void setIcon(String icon) {
+        this.icon = icon;
+    }
+
+    public List<SysMenu> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<SysMenu> children) {
+        this.children = children;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("menuId", getMenuId())
+                .append("menuName", getMenuName())
+                .append("parentId", getParentId())
+                .append("orderNum", getOrderNum())
+                .append("path", getPath())
+                .append("component", getComponent())
+                .append("isFrame", getIsFrame())
+                .append("IsCache", getIsCache())
+                .append("menuType", getMenuType())
+                .append("visible", getVisible())
+                .append("status ", getStatus())
+                .append("perms", getPerms())
+                .append("icon", getIcon())
+                .append("createBy", getCreateBy())
+                .append("createTime", getCreateTime())
+                .append("updateBy", getUpdateBy())
+                .append("updateTime", getUpdateTime())
+                .append("remark", getRemark())
+                .toString();
+    }
+}

+ 149 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysRole.java

@@ -0,0 +1,149 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.Set;
+
+/**
+ * 角色表 sys_role
+ *
+ * @author semi
+ */
+public class SysRole extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @TableId
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 角色权限
+     */
+    private String roleKey;
+
+    /**
+     * 角色排序
+     */
+    private Integer roleSort;
+
+    /**
+     * 角色状态(1正常 0停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0存在 1删除)
+     */
+    private String delFlag;
+
+    /**
+     * 角色菜单权限
+     */
+    private Set<String> permissions;
+
+    public SysRole() {
+
+    }
+
+    public SysRole(Long roleId) {
+        this.roleId = roleId;
+    }
+
+    public Long getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId) {
+        this.roleId = roleId;
+    }
+
+    public boolean isAdmin() {
+        return isAdmin(this.roleId);
+    }
+
+    public static boolean isAdmin(Long roleId) {
+        return roleId != null && 1L == roleId;
+    }
+
+    @NotBlank(message = "角色名称不能为空")
+    @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(String roleName) {
+        this.roleName = roleName;
+    }
+
+    @NotBlank(message = "权限字符不能为空")
+    @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
+    public String getRoleKey() {
+        return roleKey;
+    }
+
+    public void setRoleKey(String roleKey) {
+        this.roleKey = roleKey;
+    }
+
+    @NotNull(message = "显示顺序不能为空")
+    public Integer getRoleSort() {
+        return roleSort;
+    }
+
+    public void setRoleSort(Integer roleSort) {
+        this.roleSort = roleSort;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getDelFlag() {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag) {
+        this.delFlag = delFlag;
+    }
+
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Set<String> permissions) {
+        this.permissions = permissions;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("roleId", getRoleId())
+                .append("roleName", getRoleName())
+                .append("roleKey", getRoleKey())
+                .append("status", getStatus())
+                .append("delFlag", getDelFlag())
+                .append("createBy", getCreateBy())
+                .append("createTime", getCreateTime())
+                .append("updateBy", getUpdateBy())
+                .append("updateTime", getUpdateTime())
+                .append("remark", getRemark())
+                .toString();
+    }
+}

+ 249 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/SysUser.java

@@ -0,0 +1,249 @@
+package com.pavis.backend.slim.project.system.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.pavis.backend.slim.common.xss.Xss;
+import com.pavis.backend.slim.framework.web.domain.BaseEntity;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户对象 sys_user
+ *
+ * @author semi
+ */
+public class SysUser extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    @TableId
+    private Long userId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String telephone;
+
+    /**
+     * 用户性别(1男 0女)
+     */
+    private String sex;
+
+    /**
+     * 用户头像
+     */
+    private String avatar;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 帐号状态(1正常 0停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(1已删除 0未删除)
+     */
+    private String delFlag;
+
+    /**
+     * 最后登录IP
+     */
+    private String lastLoginIp;
+
+    /**
+     * 最后登录时间
+     */
+    private Date lastLoginDate;
+
+    /**
+     * 角色对象
+     */
+    private List<SysRole> roles;
+
+
+    public SysUser() {
+
+    }
+
+    public SysUser(Long userId) {
+        this.userId = userId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public boolean isAdmin() {
+        return isAdmin(this.userId);
+    }
+
+    public boolean isDeleted() {
+        return StringUtils.isNotBlank(this.delFlag) && StringUtils.equals("1", this.delFlag);
+    }
+
+    public boolean isAvailable() {
+        return StringUtils.isNotBlank(this.status) && StringUtils.equals("1", this.status);
+    }
+
+    public static boolean isAdmin(Long userId) {
+        return userId != null && 1L == userId;
+    }
+
+    @Xss(message = "用户昵称不能包含脚本字符")
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    @Xss(message = "用户账号不能包含脚本字符")
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
+    public String getTelephone() {
+        return telephone;
+    }
+
+    public void setTelephone(String telephone) {
+        this.telephone = telephone;
+    }
+
+    public String getSex() {
+        return sex;
+    }
+
+    public void setSex(String sex) {
+        this.sex = sex;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getDelFlag() {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag) {
+        this.delFlag = delFlag;
+    }
+
+    public String getLastLoginIp() {
+        return lastLoginIp;
+    }
+
+    public void setLastLoginIp(String loginIp) {
+        this.lastLoginIp = loginIp;
+    }
+
+    public Date getLastLoginDate() {
+        return lastLoginDate;
+    }
+
+    public void setLastLoginDate(Date loginDate) {
+        this.lastLoginDate = loginDate;
+    }
+
+    public List<SysRole> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(List<SysRole> roles) {
+        this.roles = roles;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("userId", getUserId())
+                .append("userName", getUserName())
+                .append("nickName", getNickName())
+                .append("email", getEmail())
+                .append("telephone", getTelephone())
+                .append("sex", getSex())
+                .append("avatar", getAvatar())
+                .append("password", getPassword())
+                .append("status", getStatus())
+                .append("delFlag", getDelFlag())
+                .append("lastLoginIp", getLastLoginIp())
+                .append("lastLoginDate", getLastLoginDate())
+                .append("createBy", getCreateBy())
+                .append("createTime", getCreateTime())
+                .append("updateBy", getUpdateBy())
+                .append("updateTime", getUpdateTime())
+                .append("remark", getRemark())
+                .toString();
+    }
+}

+ 91 - 0
src/main/java/com/pavis/backend/slim/project/system/domain/vo/TreeFile.java

@@ -0,0 +1,91 @@
+package com.pavis.backend.slim.project.system.domain.vo;
+
+
+import com.pavis.backend.slim.project.system.domain.SysFile;
+
+import java.util.List;
+
+/**
+ * @author semi
+ * @create 2023-05-19 17:13
+ */
+public class TreeFile {
+    /**
+     * id
+     */
+    public String id;
+    /**
+     * 名称
+     */
+    public String name;
+    /**
+     * 路径
+     */
+    public String path;
+    /**
+     * 父id ,根节点为空
+     */
+    public String parentId;
+
+    /**
+     * 文件信息
+     */
+    public SysFile sysFile;
+
+    /**
+     * 子节点信息
+     */
+    public List<TreeFile> childList;
+
+    public TreeFile() {
+
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public String getParentId() {
+        return parentId;
+    }
+
+    public void setParentId(String parentId) {
+        this.parentId = parentId;
+    }
+
+    public SysFile getSysFile() {
+        return sysFile;
+    }
+
+    public void setSysFile(SysFile sysFile) {
+        this.sysFile = sysFile;
+    }
+
+    public List<TreeFile> getChildList() {
+        return childList;
+    }
+
+    public void setChildList(List<TreeFile> childList) {
+        this.childList = childList;
+    }
+}

+ 12 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysEntityInstanceMapper.java

@@ -0,0 +1,12 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-06-01 11:20
+ */
+@Mapper
+public interface SysEntityInstanceMapper extends BaseMapper<SysEntityInstanceMapper> {
+}

+ 13 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysEntityMapper.java

@@ -0,0 +1,13 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-06-01 11:19
+ */
+@Mapper
+public interface SysEntityMapper extends BaseMapper<SysEntity> {
+}

+ 25 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysFileMapper.java

@@ -0,0 +1,25 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysFile;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author semi
+ * @create 2023-04-24 14:08
+ */
+@Mapper
+public interface SysFileMapper extends BaseMapper<SysFile> {
+
+    /**
+     * 根据知识库id获取文件信息
+     *
+     * @param kbId   知识库id
+     * @param userId 用户id
+     * @return 文件列表
+     */
+    List<SysFile> selectByKbIdAndUserId(@Param("kbId") String kbId, @Param("userId") Long userId);
+}

+ 14 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysKbFileMapper.java

@@ -0,0 +1,14 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysKbFile;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-05-09 11:15
+ */
+@Mapper
+public interface SysKbFileMapper extends BaseMapper<SysKbFile> {
+
+}

+ 13 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysKbMapper.java

@@ -0,0 +1,13 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysKb;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-05-08 17:08
+ */
+@Mapper
+public interface SysKbMapper extends BaseMapper<SysKb> {
+}

+ 13 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysKgMapper.java

@@ -0,0 +1,13 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysKg;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-06-01 11:18
+ */
+@Mapper
+public interface SysKgMapper extends BaseMapper<SysKg> {
+}

+ 13 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysMenuMapper.java

@@ -0,0 +1,13 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysMenu;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-04-21 15:45
+ */
+@Mapper
+public interface SysMenuMapper extends BaseMapper<SysMenu> {
+}

+ 13 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysRoleMapper.java

@@ -0,0 +1,13 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysRole;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-04-21 15:41
+ */
+@Mapper
+public interface SysRoleMapper extends BaseMapper<SysRole> {
+}

+ 29 - 0
src/main/java/com/pavis/backend/slim/project/system/mapper/SysUserMapper.java

@@ -0,0 +1,29 @@
+package com.pavis.backend.slim.project.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author semi
+ * @create 2023-04-21 09:16
+ */
+@Mapper
+public interface SysUserMapper extends BaseMapper<SysUser> {
+
+    /**
+     * 根据用户名获取用户信息
+     *
+     * @param username 用户名
+     * @return 用户信息
+     */
+    SysUser selectUserByUserName(String username);
+
+    /**
+     * 根据用户id获取用户信息
+     *
+     * @param userId 用户id
+     * @return 用户信息
+     */
+    SysUser selectUserById(Long userId);
+}

+ 73 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysFileService.java

@@ -0,0 +1,73 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysFile;
+import com.pavis.backend.slim.project.system.domain.vo.TreeFile;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * @author semi
+ * @create 2023-04-24 14:08
+ */
+public interface SysFileService extends IService<SysFile> {
+
+    /**
+     * 单个文件上传
+     *
+     * @param file 需要上传的文件
+     * @return 文件信息
+     */
+    SysFile uploadFile(MultipartFile file);
+
+    /**
+     * 单个文件上传
+     *
+     * @param file  需要上传的文件
+     * @param icon  文件图标
+     * @param path  文件路径
+     * @param isDir 是否是文件夹
+     * @return 文件信息
+     */
+    SysFile uploadFile(MultipartFile file, String icon, String path, boolean isDir);
+
+    /**
+     * 多个文件上传
+     *
+     * @param files 需要上传的文件列表
+     * @return 文件列表信息
+     */
+    List<SysFile> uploadFiles(MultipartFile[] files);
+
+    /**
+     * 获取已上传的文件
+     *
+     * @return 已上传的文件列表
+     */
+    List<SysFile> listFiles();
+
+    /**
+     * 获取知识库文件的树形结构
+     *
+     * @param kbId 知识库id
+     * @return 树形结构文件列表
+     */
+    List<TreeFile> listTreeFiles(String kbId);
+
+    /**
+     * 文件文件路径列表获取文件树形结构数据
+     *
+     * @param sysFileList 文件列表
+     * @return 文件树形结构数据
+     */
+    List<TreeFile> getTreeFiles(List<SysFile> sysFileList);
+
+    /**
+     * 预览文件
+     *
+     * @param key 文件key值
+     * @return 文件可访问的url
+     */
+    String preview(String key);
+}

+ 33 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysKbFileService.java

@@ -0,0 +1,33 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysFile;
+import com.pavis.backend.slim.project.system.domain.SysKbFile;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * @author semi
+ * @create 2023-05-09 11:14
+ */
+public interface SysKbFileService extends IService<SysKbFile> {
+
+    /**
+     * 为知识库添加文档
+     *
+     * @param kbId  知识库id
+     * @param file  文件
+     * @param icon  文件图标
+     * @param path  文件所属卢金
+     * @param isDir 是否是文件夹
+     * @return 文件信息
+     */
+    SysFile addDoc(String kbId, MultipartFile file, String icon, String path, Boolean isDir);
+
+    /**
+     * 删除知识库文档
+     *
+     * @param kbId   知识库id
+     * @param fileId 知识库文档id
+     */
+    void delDoc(String kbId, String fileId);
+}

+ 46 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysKbService.java

@@ -0,0 +1,46 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysKb;
+
+import java.util.List;
+
+/**
+ * @author semi
+ * @create 2023-05-08 17:08
+ */
+public interface SysKbService extends IService<SysKb> {
+
+    /**
+     * 创建知识库
+     *
+     * @param kb 知识库信息
+     * @return 知识库信息
+     */
+    SysKb create(SysKb kb);
+
+    /**
+     * 获取知识库列表
+     *
+     * @param userId 用户id
+     * @return 知识库列表
+     */
+    List<SysKb> selectByUser(Long userId);
+
+    /**
+     * 校验知识库名称是否重复
+     *
+     * @param kb 知识库信息
+     * @return 是否重复
+     */
+    boolean checkKbNameExist(SysKb kb);
+
+    /**
+     * 删除知识库
+     *
+     * @param kbId   知识库id
+     * @param delAll 是否删除知识库内所有文件
+     * @return 是否删除成功
+     */
+    boolean delete(String kbId, boolean delAll);
+}

+ 19 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysKgService.java

@@ -0,0 +1,19 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysKg;
+
+/**
+ * @author semi
+ * @create 2023-06-01 11:30
+ */
+public interface SysKgService extends IService<SysKg> {
+
+    /**
+     * 创建图谱
+     *
+     * @param kg 图谱信息
+     * @return 已创建的图谱信息
+     */
+    SysKg create(SysKg kg);
+}

+ 37 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysMenuService.java

@@ -0,0 +1,37 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysMenu;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author semi
+ * @create 2023-04-21 15:45
+ */
+public interface SysMenuService extends IService<SysMenu> {
+
+    /**
+     * 根据角色id获取该菜单权限集合
+     *
+     * @param roleId 角色id
+     * @return 权限集合
+     */
+    Set<String> selectMenuPermsByRoleId(Long roleId);
+
+    /**
+     * 根据用户id获取菜单权限集合
+     *
+     * @param userId 用户id
+     * @return 权限集合
+     */
+    Set<String> selectMenuPermsByUserId(Long userId);
+
+    /**
+     * 根据用户id获取菜单集合
+     * @param userId 用户id
+     * @return 菜单列表
+     */
+    List<SysMenu> selectMenuTreeByUserId(Long userId);
+}

+ 20 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysRoleService.java

@@ -0,0 +1,20 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysRole;
+
+import java.util.Set;
+
+/**
+ * @author semi
+ * @create 2023-04-21 15:40
+ */
+public interface SysRoleService extends IService<SysRole> {
+
+    /**
+     * 根据用户id获取用户角色权限
+     * @param userId 用户id
+     * @return 用户角色权限集
+     */
+    Set<String> selectRolePermsByUserId(Long userId);
+}

+ 19 - 0
src/main/java/com/pavis/backend/slim/project/system/service/SysUserService.java

@@ -0,0 +1,19 @@
+package com.pavis.backend.slim.project.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+
+/**
+ * @author semi
+ * @create 2023-04-21 09:58
+ */
+public interface SysUserService extends IService<SysUser> {
+
+    /**
+     * 根据用户名获取用户信息
+     *
+     * @param username 用户名
+     * @return 用户信息
+     */
+    SysUser selectByUserName(String username);
+}

+ 217 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysFileServiceImpl.java

@@ -0,0 +1,217 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.MD5;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.common.constant.Constant;
+import com.pavis.backend.slim.common.exception.ServiceException;
+import com.pavis.backend.slim.common.utils.FileUtils;
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.framework.minio.MinioStorage;
+import com.pavis.backend.slim.project.system.domain.SysFile;
+import com.pavis.backend.slim.project.system.domain.vo.TreeFile;
+import com.pavis.backend.slim.project.system.mapper.SysFileMapper;
+import com.pavis.backend.slim.project.system.service.SysFileService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author semi
+ * @create 2023-04-24 14:08
+ */
+@Service
+public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {
+
+    @Autowired
+    MinioStorage storage;
+
+    @Override
+    public SysFile uploadFile(MultipartFile file) {
+        SysFile sysFile = uploadFile(file, "", Constant.DIR_SEP, false);
+        baseMapper.insert(sysFile);
+        return sysFile;
+    }
+
+    @Override
+    public SysFile uploadFile(MultipartFile file, String icon, String path, boolean isDir) {
+        System.out.println("icon:" + icon);
+        SysFile sysFile = new SysFile();
+        // 获取上传文件的用户id
+        sysFile.setUserId(SecurityUtils.getUserId());
+        // 设置是否是文件夹
+        sysFile.setDir(isDir);
+        // 设置文件图标
+        sysFile.setIcon(icon);
+        // 设置基本信息
+        sysFile.setCreateBy(SecurityUtils.getUsername());
+        sysFile.setCreateTime(DateUtil.date());
+        // 判断文件路径:/a/b/或/
+        if (StringUtils.isBlank(path)) {
+            throw new ServiceException("文件路径不能为空!");
+        } else {
+            if (!StringUtils.startsWith(path, Constant.DIR_SEP) || !StringUtils.endsWith(path, Constant.DIR_SEP)) {
+                throw new ServiceException("文件路径必须以“/”开头和结尾!");
+            }
+        }
+        if (!isDir) {
+            if (file == null || file.isEmpty() || file.getSize() <= 0) {
+                throw new ServiceException("上传文件不能为空!");
+            }
+            String originalName = file.getOriginalFilename();
+            if (StringUtils.isEmpty(originalName)) {
+                throw new ServiceException("上传文件名不能为空!");
+            }
+            if (file.getSize() > Constant.FILE_MAX_SIZE) {
+                throw new ServiceException("文件过大,小文件上传最大支持5M!");
+            }
+            // 获取上传文件原文件名称
+            sysFile.setOriginalName(originalName);
+            // 生成新的文件名称
+            sysFile.setName(FileUtils.genFileName(originalName));
+            // 根据文件名称生成新的oss key
+            sysFile.setObjectKey(storage.genKeyWithPath(sysFile.getUserId(), sysFile.getName(), path));
+            // 根据文件key值生成文件url
+            sysFile.setUrl(storage.getObjectUrl(sysFile.getObjectKey()));
+            // 设置文件所属路径:/a/b/1.jpg或/1.jpg
+            sysFile.setPath(path + sysFile.getName());
+            // 获取文件大小
+            sysFile.setSize(file.getSize());
+            // 获取文件后缀
+            sysFile.setSuffix(StringUtils.substringAfterLast(sysFile.getOriginalName(), "."));
+            // 获取文件类型
+            sysFile.setType(FileUtils.getFileType(sysFile.getSuffix()));
+            // 获取文件md5
+            try (InputStream is = file.getInputStream()) {
+                sysFile.setIdentifier(MD5.create().digestHex16(is));
+            } catch (IOException e) {
+                throw new ServiceException("文件流获取失败!");
+            }
+            // 存储到minio
+            try {
+                storage.putObject(sysFile.getObjectKey(), file);
+            } catch (IOException | NoSuchAlgorithmException e) {
+                throw new ServiceException(e.getMessage());
+            } catch (InvalidKeyException e) {
+                throw new ServiceException("文件的key无效!");
+            }
+        } else {
+            if (StringUtils.equals(path, Constant.DIR_SEP)) {
+                throw new ServiceException("请填写文件夹名称并以“/”结尾!");
+            }
+            // 文件夹 /a/b/->/a/b->b
+            String dirName = StrUtil.subAfter(StrUtil.subBefore(path, Constant.DIR_SEP, true), Constant.DIR_SEP, true);
+            sysFile.setName(dirName);
+            // 设置文件夹路径
+            sysFile.setPath(path);
+            // 设置大小
+            sysFile.setSize(0L);
+            // 设置类型
+            sysFile.setType(FileUtils.TYPE_DIR);
+        }
+        baseMapper.insert(sysFile);
+        return sysFile;
+    }
+
+    @Override
+    public List<SysFile> uploadFiles(MultipartFile[] files) {
+        if (files != null && files.length > 0) {
+            List<SysFile> fileList = new ArrayList<>();
+            for (MultipartFile file : files) {
+                fileList.add(uploadFile(file));
+            }
+            return fileList;
+        }
+        return null;
+    }
+
+    @Override
+    public List<SysFile> listFiles() {
+        long userId = SecurityUtils.getUserId();
+        return query().eq("user_id", userId).list();
+    }
+
+    @Override
+    public List<TreeFile> listTreeFiles(String kbId) {
+        // 获取文件列表
+        Long userId = SecurityUtils.getUserId();
+        if (null == userId || 0L == userId) {
+            throw new ServiceException("用户登录信息无效,请重新登录!");
+        }
+        if (StringUtils.isBlank(kbId)) {
+            throw new ServiceException("知识库id不能为空!");
+        }
+        List<SysFile> sysFileList = baseMapper.selectByKbIdAndUserId(kbId, userId);
+        if (sysFileList == null || sysFileList.size() == 0) {
+            return null;
+        }
+        // 获取父节点
+        return getTreeFiles(sysFileList);
+    }
+
+    /**
+     * 文件文件路径列表获取文件树形结构数据
+     *
+     * @param sysFileList 文件列表
+     * @return 文件树形结构数据
+     */
+    @Override
+    public List<TreeFile> getTreeFiles(List<SysFile> sysFileList) {
+        List<TreeFile> treeFiles = new ArrayList<>();
+        for (SysFile sysFile : sysFileList) {
+            treeFiles.add(getTreeFile(sysFile, sysFileList));
+        }
+        System.out.println("1-tree-files:" + JSON.toJSONString(treeFiles));
+        // 获取父节点
+        return treeFiles
+                .stream()
+                .filter(m -> m.getParentId() == null)
+                .peek((m) -> m.setChildList(FileUtils.getChildrens(m, treeFiles)))
+                .collect(Collectors.toList());
+    }
+
+    public TreeFile getTreeFile(SysFile sysFile, List<SysFile> sysFileList) {
+        TreeFile treeFile = new TreeFile();
+        treeFile.setId(sysFile.getFileId());
+        treeFile.setName(sysFile.getName());
+        treeFile.setPath(sysFile.getPath());
+        treeFile.setSysFile(sysFile);
+        // /a/,/a/b/,/a/b/c/,/1.jpg
+        String path = StrUtil.subAfter(sysFile.getPath(), Constant.DIR_SEP, false);
+        System.out.println("getTreeFile->path:" + path);
+        if (path.split(Constant.DIR_SEP).length == 1) {
+            treeFile.setParentId(null);
+            return treeFile;
+        } else {
+            String tmpPath = StrUtil.subBefore(sysFile.getPath(), Constant.DIR_SEP, true);
+            String targetPath = "";
+            if (sysFile.getDir()) {
+                targetPath = StrUtil.subBefore(tmpPath, Constant.DIR_SEP, true) + Constant.DIR_SEP;
+            } else {
+                targetPath = tmpPath + Constant.DIR_SEP;
+            }
+            for (SysFile file : sysFileList) {
+                if (file.getDir() && StringUtils.equals(file.getPath(), targetPath)) {
+                    treeFile.setParentId(file.getFileId());
+                    return treeFile;
+                }
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public String preview(String key) {
+        return storage.preview(key);
+    }
+}

+ 59 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysKbFileServiceImpl.java

@@ -0,0 +1,59 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.common.exception.ServiceException;
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.project.system.domain.SysFile;
+import com.pavis.backend.slim.project.system.domain.SysKbFile;
+import com.pavis.backend.slim.project.system.mapper.SysKbFileMapper;
+import com.pavis.backend.slim.project.system.service.SysFileService;
+import com.pavis.backend.slim.project.system.service.SysKbFileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * @author semi
+ * @create 2023-05-09 11:15
+ */
+@Service
+public class SysKbFileServiceImpl extends ServiceImpl<SysKbFileMapper, SysKbFile> implements SysKbFileService {
+
+    @Autowired
+    private SysFileService fileService;
+
+    @Override
+    public SysFile addDoc(String kbId, MultipartFile file, String icon, String path, Boolean isDir) {
+        SysFile sysFile = fileService.uploadFile(file, icon, path, isDir);
+        SysKbFile kbFile = new SysKbFile();
+        kbFile.setKbId(kbId);
+        kbFile.setFileId(sysFile.getFileId());
+        kbFile.setUserId(SecurityUtils.getUserId());
+        kbFile.setCreateBy(SecurityUtils.getUsername());
+        kbFile.setCreateTime(DateUtil.date());
+        baseMapper.insert(kbFile);
+        return sysFile;
+    }
+
+    @Override
+    public void delDoc(String kbId, String fileId) {
+        Long userId = SecurityUtils.getUserId();
+        if (userId == null || userId == 0) {
+            throw new ServiceException("用户登录信息不存在,请登录后重试!");
+        }
+        QueryWrapper<SysKbFile> wrapper = new QueryWrapper<>();
+        wrapper
+                .eq("user_id", userId)
+                .eq("kb_id", kbId)
+                .eq("file_id", fileId);
+        List<SysKbFile> sysKbFiles = baseMapper.selectList(wrapper);
+        if (sysKbFiles == null || sysKbFiles.size() == 0) {
+            throw new ServiceException("文件不存在!");
+        }
+        baseMapper.delete(wrapper);
+    }
+}

+ 50 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysKbServiceImpl.java

@@ -0,0 +1,50 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.common.utils.SecurityUtils;
+import com.pavis.backend.slim.project.system.domain.SysKb;
+import com.pavis.backend.slim.project.system.mapper.SysKbMapper;
+import com.pavis.backend.slim.project.system.service.SysKbService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author semi
+ * @create 2023-05-08 17:09
+ */
+@Service
+public class SysKbServiceImpl extends ServiceImpl<SysKbMapper, SysKb> implements SysKbService {
+
+    @Override
+    public SysKb create(SysKb kb) {
+        kb.setCreateBy(SecurityUtils.getUsername());
+        kb.setCreateTime(DateUtil.date());
+        kb.setUrl("/kb/" + kb.getKbId());
+        baseMapper.insert(kb);
+        return kb;
+    }
+
+    @Override
+    public List<SysKb> selectByUser(Long userId) {
+        return query().eq("user_id", userId).list();
+    }
+
+
+    @Override
+    public boolean checkKbNameExist(SysKb kb) {
+        List<SysKb> list = query().eq("user_id", kb.getUserId()).eq("name", kb.getName()).list();
+        return list.size() > 0;
+    }
+
+    @Override
+    public boolean delete(String kbId, boolean delAll) {
+        if (delAll) {
+
+        } else {
+
+        }
+        return false;
+    }
+}

+ 21 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysKgServiceImpl.java

@@ -0,0 +1,21 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.project.system.domain.SysKg;
+import com.pavis.backend.slim.project.system.mapper.SysKgMapper;
+import com.pavis.backend.slim.project.system.service.SysKgService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author semi
+ * @create 2023-06-01 11:31
+ */
+@Service
+public class SysKgServiceImpl extends ServiceImpl<SysKgMapper,SysKg> implements SysKgService {
+
+
+    @Override
+    public SysKg create(SysKg kg) {
+        return null;
+    }
+}

+ 33 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysMenuServiceImpl.java

@@ -0,0 +1,33 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.project.system.domain.SysMenu;
+import com.pavis.backend.slim.project.system.mapper.SysMenuMapper;
+import com.pavis.backend.slim.project.system.service.SysMenuService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author semi
+ * @create 2023-04-21 15:45
+ */
+@Service
+public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
+
+    @Override
+    public Set<String> selectMenuPermsByRoleId(Long roleId) {
+        return null;
+    }
+
+    @Override
+    public Set<String> selectMenuPermsByUserId(Long userId) {
+        return null;
+    }
+
+    @Override
+    public List<SysMenu> selectMenuTreeByUserId(Long userId) {
+        return null;
+    }
+}

+ 21 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysRoleServiceImpl.java

@@ -0,0 +1,21 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.project.system.domain.SysRole;
+import com.pavis.backend.slim.project.system.mapper.SysRoleMapper;
+import com.pavis.backend.slim.project.system.service.SysRoleService;
+import org.springframework.stereotype.Service;
+
+import java.util.Set;
+
+/**
+ * @author semi
+ * @create 2023-04-21 15:41
+ */
+@Service
+public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
+    @Override
+    public Set<String> selectRolePermsByUserId(Long userId) {
+        return null;
+    }
+}

+ 19 - 0
src/main/java/com/pavis/backend/slim/project/system/service/impl/SysUserServiceImpl.java

@@ -0,0 +1,19 @@
+package com.pavis.backend.slim.project.system.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.pavis.backend.slim.project.system.domain.SysUser;
+import com.pavis.backend.slim.project.system.mapper.SysUserMapper;
+import com.pavis.backend.slim.project.system.service.SysUserService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author semi
+ * @create 2023-04-21 09:59
+ */
+@Service
+public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
+    @Override
+    public SysUser selectByUserName(String username) {
+        return baseMapper.selectUserByUserName(username);
+    }
+}

+ 41 - 0
src/main/resources/application-dev.yml

@@ -0,0 +1,41 @@
+server:
+  port: 10011
+# 通用配置
+pavis:
+  profile: /Users/alibct/Downloads/project/pavis/uploadPath
+#minio配置
+minio:
+  #对象存储服务的URL
+  url: http://localhost:9000/
+  #Access key账户
+  accessKey: 2TlzVcrX1jH2oWhz
+  #Secret key密码
+  secretKey: ZfmZgtZO4g1L8iMmVB8DeARCSdyxzI8G
+  bucketName: default
+spring:
+  datasource:
+    dynamic:
+      primary: master #设置默认的数据源或者数据源组,默认值即为master
+      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
+      datasource:
+        master:
+          url: jdbc:mysql://localhost:3306/slim
+          username: root
+          password: password
+          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
+        slave_1:
+          url: jdbc:mysql://localhost:3307/slim
+          username: root
+          password: password
+          driver-class-name: com.mysql.cj.jdbc.Driver
+        slave_2:
+          url: jdbc:mysql://localhost:3308/slim
+          username: root
+          password: password
+          driver-class-name: com.mysql.cj.jdbc.Driver
+  # redis 配置
+  redis:
+    # 地址
+    host: localhost
+    # 端口,默认为6379
+    port: 6379

+ 1 - 0
src/main/resources/application-local.yml

@@ -0,0 +1 @@
+

+ 1 - 0
src/main/resources/application-prod.yml

@@ -0,0 +1 @@
+

+ 43 - 0
src/main/resources/application.yml

@@ -0,0 +1,43 @@
+spring:
+  profiles:
+    active: dev
+  # 国际化资源文件路径
+  messages:
+    basename: i18n/messages
+  # 文件上传
+  servlet:
+    multipart:
+      # 单个文件大小
+      max-file-size:  10MB
+      # 设置总上传的文件大小
+      max-request-size:  20MB
+# 令牌配置
+token:
+  header: Authorization
+  secret: qwertyuiopasdfghjklzxcvbnm
+  expireTime: 30
+# 用户配置
+user:
+  password:
+    # 密码最大错误次数
+    maxRetryCount: 5
+    # 密码锁定时间(默认10分钟)
+    lockTime: 10
+knife4j:
+  enable: true
+  openapi:
+    title: api接口文档
+    description: "知识图谱后端接口文档"
+    version: v0.1
+    group:
+      default:
+        group-name: 接口测试
+        api-rule: package
+        api-rule-resources:
+          - com.pavis.backend.slim.project.system.controller
+# mybatis-plus 配置
+mybatis-plus:
+  type-aliases-package: com.pavis.backend.slim.project.**.domain
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

+ 38 - 0
src/main/resources/i18n/messages.properties

@@ -0,0 +1,38 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+login.blocked=很遗憾,访问IP已被列入系统黑名单
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 35 - 0
src/main/resources/mapper/system/SysFileMapper.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.pavis.backend.slim.project.system.mapper.SysFileMapper">
+
+    <resultMap type="SysFile" id="SysFileResult">
+        <id property="fileId" column="file_id"/>
+        <result property="userId" column="user_id"/>
+        <result property="name" column="name"/>
+        <result property="originalName" column="original_name"/>
+        <result property="objectKey" column="object_key"/>
+        <result property="url" column="url"/>
+        <result property="path" column="path"/>
+        <result property="size" column="size"/>
+        <result property="type" column="type"/>
+        <result property="suffix" column="suffix"/>
+        <result property="icon" column="icon"/>
+        <result property="identifier" column="identifier"/>
+        <result property="isDir" column="is_dir"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectByKbIdAndUserId" resultMap="SysFileResult">
+        select *
+        from sys_file sf
+                 left join sys_kb_file skf on sf.file_id = skf.file_id
+        where skf.kb_id = #{kbId}
+          and skf.user_id = #{userId}
+    </select>
+
+</mapper> 

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä