1
0
Преглед на файлове

frist commit:智能BI项目前后端初始化

yang yi преди 9 месеца
ревизия
822b37f5c0
променени са 49 файла, в които са добавени 3701 реда и са изтрити 0 реда
  1. 27 0
      .gitignore
  2. 24 0
      BI_front/.gitignore
  3. 3 0
      BI_front/.vscode/extensions.json
  4. 5 0
      BI_front/README.md
  5. 13 0
      BI_front/index.html
  6. 1061 0
      BI_front/package-lock.json
  7. 18 0
      BI_front/package.json
  8. 1 0
      BI_front/public/vite.svg
  9. 30 0
      BI_front/src/App.vue
  10. 1 0
      BI_front/src/assets/vue.svg
  11. 43 0
      BI_front/src/components/HelloWorld.vue
  12. 5 0
      BI_front/src/main.js
  13. 79 0
      BI_front/src/style.css
  14. 7 0
      BI_front/vite.config.js
  15. 104 0
      serve/pom.xml
  16. 31 0
      serve/sql/init.sql
  17. 18 0
      serve/src/main/java/space/anyi/BI/BIApplication.java
  18. 27 0
      serve/src/main/java/space/anyi/BI/config/MybatisplusConfig.java
  19. 87 0
      serve/src/main/java/space/anyi/BI/config/SecurityConfig.java
  20. 67 0
      serve/src/main/java/space/anyi/BI/config/SwaggerConfig.java
  21. 82 0
      serve/src/main/java/space/anyi/BI/constant/SystemConstants.java
  22. 60 0
      serve/src/main/java/space/anyi/BI/controller/ChartController.java
  23. 36 0
      serve/src/main/java/space/anyi/BI/controller/UserController.java
  24. 271 0
      serve/src/main/java/space/anyi/BI/entity/Chart.java
  25. 65 0
      serve/src/main/java/space/anyi/BI/entity/LoginUserDetails.java
  26. 181 0
      serve/src/main/java/space/anyi/BI/entity/ResponseResult.java
  27. 249 0
      serve/src/main/java/space/anyi/BI/entity/User.java
  28. 75 0
      serve/src/main/java/space/anyi/BI/entity/vo/UserInfo.java
  29. 31 0
      serve/src/main/java/space/anyi/BI/exception/SystemException.java
  30. 76 0
      serve/src/main/java/space/anyi/BI/filter/JWTAuthenticationTokenFilter.java
  31. 30 0
      serve/src/main/java/space/anyi/BI/handler/exception/GlobalExceptionHandler.java
  32. 37 0
      serve/src/main/java/space/anyi/BI/handler/mybatispuls/MyMetaObjectHandler.java
  33. 30 0
      serve/src/main/java/space/anyi/BI/handler/security/AccessDeniedHandlerImpl.java
  34. 40 0
      serve/src/main/java/space/anyi/BI/handler/security/AuthenticationEntryPointImpl.java
  35. 18 0
      serve/src/main/java/space/anyi/BI/mapper/ChartMapper.java
  36. 18 0
      serve/src/main/java/space/anyi/BI/mapper/UserMapper.java
  37. 13 0
      serve/src/main/java/space/anyi/BI/service/ChartService.java
  38. 17 0
      serve/src/main/java/space/anyi/BI/service/UserService.java
  39. 22 0
      serve/src/main/java/space/anyi/BI/service/impl/ChartServiceImpl.java
  40. 41 0
      serve/src/main/java/space/anyi/BI/service/impl/UserDetailsServiceImpl.java
  41. 95 0
      serve/src/main/java/space/anyi/BI/service/impl/UserServiceImpl.java
  42. 36 0
      serve/src/main/java/space/anyi/BI/util/BeanCopyUtil.java
  43. 112 0
      serve/src/main/java/space/anyi/BI/util/JwtUtil.java
  44. 252 0
      serve/src/main/java/space/anyi/BI/util/RedisCache.java
  45. 40 0
      serve/src/main/java/space/anyi/BI/util/SecurityUtils.java
  46. 44 0
      serve/src/main/java/space/anyi/BI/util/WebUtils.java
  47. 29 0
      serve/src/main/resources/application.yaml
  48. 26 0
      serve/src/main/resources/mapper/ChartMapper.xml
  49. 24 0
      serve/src/main/resources/mapper/UserMapper.xml

+ 27 - 0
.gitignore

@@ -0,0 +1,27 @@
+# created by virtualenv automatically
+
+
+**/.idea
+**/node_modules
+**/.gradle
+**/.mvn
+**/*.iml
+**/*.log
+**/*.rar
+**/*.png
+**/*.jpg
+**/*.xls
+**/*.xlsx
+**/*.zip
+**/*.rar
+**/*.o
+**/*.exe
+**/out/**
+**/target/**
+**/build/**
+/*
+
+!/BI_front/
+!/serve/
+
+!.gitignore

+ 24 - 0
BI_front/.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
BI_front/.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 5 - 0
BI_front/README.md

@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

+ 13 - 0
BI_front/index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 1061 - 0
BI_front/package-lock.json

@@ -0,0 +1,1061 @@
+{
+  "name": "bi-front",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "bi-front",
+      "version": "0.0.0",
+      "dependencies": {
+        "vue": "^3.5.13"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^5.2.1",
+        "vite": "^6.0.1"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.26.2",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz",
+      "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+      "dependencies": {
+        "@babel/types": "^7.26.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz",
+      "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.25.9",
+        "@babel/helper-validator-identifier": "^7.25.9"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
+      "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
+      "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
+      "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
+      "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
+      "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
+      "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
+      "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
+      "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
+      "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
+      "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
+      "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
+      "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
+      "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
+      "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
+      "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
+      "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
+      "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
+      "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
+      "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
+      "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
+      "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
+      "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
+      "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
+      "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
+      "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
+      "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
+      "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
+      "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
+      "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
+      "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
+      "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
+      "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
+      "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
+      "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
+      "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
+      "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
+      "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
+      "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
+      "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
+      "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+      "dev": true
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
+      "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+      "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/shared": "3.5.13",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+      "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/compiler-core": "3.5.13",
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/compiler-ssr": "3.5.13",
+        "@vue/shared": "3.5.13",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.11",
+        "postcss": "^8.4.48",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+      "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
+      "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+      "dependencies": {
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+      "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+      "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.13",
+        "@vue/runtime-core": "3.5.13",
+        "@vue/shared": "3.5.13",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+      "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.13",
+        "@vue/shared": "3.5.13"
+      },
+      "peerDependencies": {
+        "vue": "3.5.13"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.0.tgz",
+      "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.24.0",
+        "@esbuild/android-arm": "0.24.0",
+        "@esbuild/android-arm64": "0.24.0",
+        "@esbuild/android-x64": "0.24.0",
+        "@esbuild/darwin-arm64": "0.24.0",
+        "@esbuild/darwin-x64": "0.24.0",
+        "@esbuild/freebsd-arm64": "0.24.0",
+        "@esbuild/freebsd-x64": "0.24.0",
+        "@esbuild/linux-arm": "0.24.0",
+        "@esbuild/linux-arm64": "0.24.0",
+        "@esbuild/linux-ia32": "0.24.0",
+        "@esbuild/linux-loong64": "0.24.0",
+        "@esbuild/linux-mips64el": "0.24.0",
+        "@esbuild/linux-ppc64": "0.24.0",
+        "@esbuild/linux-riscv64": "0.24.0",
+        "@esbuild/linux-s390x": "0.24.0",
+        "@esbuild/linux-x64": "0.24.0",
+        "@esbuild/netbsd-x64": "0.24.0",
+        "@esbuild/openbsd-arm64": "0.24.0",
+        "@esbuild/openbsd-x64": "0.24.0",
+        "@esbuild/sunos-x64": "0.24.0",
+        "@esbuild/win32-arm64": "0.24.0",
+        "@esbuild/win32-ia32": "0.24.0",
+        "@esbuild/win32-x64": "0.24.0"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.14",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.14.tgz",
+      "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.8",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/postcss": {
+      "version": "8.4.49",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz",
+      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.27.4",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.27.4.tgz",
+      "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "1.0.6"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.27.4",
+        "@rollup/rollup-android-arm64": "4.27.4",
+        "@rollup/rollup-darwin-arm64": "4.27.4",
+        "@rollup/rollup-darwin-x64": "4.27.4",
+        "@rollup/rollup-freebsd-arm64": "4.27.4",
+        "@rollup/rollup-freebsd-x64": "4.27.4",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.27.4",
+        "@rollup/rollup-linux-arm-musleabihf": "4.27.4",
+        "@rollup/rollup-linux-arm64-gnu": "4.27.4",
+        "@rollup/rollup-linux-arm64-musl": "4.27.4",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4",
+        "@rollup/rollup-linux-riscv64-gnu": "4.27.4",
+        "@rollup/rollup-linux-s390x-gnu": "4.27.4",
+        "@rollup/rollup-linux-x64-gnu": "4.27.4",
+        "@rollup/rollup-linux-x64-musl": "4.27.4",
+        "@rollup/rollup-win32-arm64-msvc": "4.27.4",
+        "@rollup/rollup-win32-ia32-msvc": "4.27.4",
+        "@rollup/rollup-win32-x64-msvc": "4.27.4",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-6.0.1.tgz",
+      "integrity": "sha512-Ldn6gorLGr4mCdFnmeAOLweJxZ34HjKnDm4HGo6P66IEqTxQb36VEdFJQENKxWjupNfoIjvRUnswjn1hpYEpjQ==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.24.0",
+        "postcss": "^8.4.49",
+        "rollup": "^4.23.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "jiti": ">=1.21.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
+      "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/compiler-sfc": "3.5.13",
+        "@vue/runtime-dom": "3.5.13",
+        "@vue/server-renderer": "3.5.13",
+        "@vue/shared": "3.5.13"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    }
+  }
+}

+ 18 - 0
BI_front/package.json

@@ -0,0 +1,18 @@
+{
+  "name": "bi-front",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "vue": "^3.5.13"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.2.1",
+    "vite": "^6.0.1"
+  }
+}

+ 1 - 0
BI_front/public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 30 - 0
BI_front/src/App.vue

@@ -0,0 +1,30 @@
+<script setup>
+import HelloWorld from './components/HelloWorld.vue'
+</script>
+
+<template>
+  <div>
+    <a href="https://vite.dev" target="_blank">
+      <img src="/vite.svg" class="logo" alt="Vite logo" />
+    </a>
+    <a href="https://vuejs.org/" target="_blank">
+      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
+    </a>
+  </div>
+  <HelloWorld msg="Vite + Vue" />
+</template>
+
+<style scoped>
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vue:hover {
+  filter: drop-shadow(0 0 2em #42b883aa);
+}
+</style>

+ 1 - 0
BI_front/src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 43 - 0
BI_front/src/components/HelloWorld.vue

@@ -0,0 +1,43 @@
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  msg: String,
+})
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Learn more about IDE Support for Vue in the
+    <a
+      href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
+      target="_blank"
+      >Vue Docs Scaling up Guide</a
+    >.
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

+ 5 - 0
BI_front/src/main.js

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')

+ 79 - 0
BI_front/src/style.css

@@ -0,0 +1,79 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 7 - 0
BI_front/vite.config.js

@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+})

+ 104 - 0
serve/pom.xml

@@ -0,0 +1,104 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>space.anyi</groupId>
+    <artifactId>BI</artifactId>
+    <version>1.0</version>
+    <packaging>jar</packaging>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+    </properties>
+
+    <parent>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <groupId>org.springframework.boot</groupId>
+        <version>2.7.16</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>5.1.49</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.4.3.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-spring-boot-starter</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+        <!--<dependency>-->
+        <!--    <groupId>io.springfox</groupId>-->
+        <!--    <artifactId>springfox-swagger2</artifactId>-->
+        <!--    <version>2.7.0</version>-->
+        <!--</dependency>-->
+        <!--<dependency>-->
+        <!--    <groupId>io.springfox</groupId>-->
+        <!--    <artifactId>springfox-swagger-ui</artifactId>-->
+        <!--    <version>2.7.0</version>-->
+        <!--</dependency>-->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>2.0.8</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.33</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <!--<build>-->
+    <!--    <plugins>-->
+    <!--        <plugin>-->
+    <!--            <groupId>org.springframework.boot</groupId>-->
+    <!--            <artifactId>spring-boot-maven-plugin</artifactId>-->
+    <!--            <configuration>-->
+    <!--            </configuration>-->
+    <!--        </plugin>-->
+    <!--    </plugins>-->
+    <!--</build>-->
+
+</project>

+ 31 - 0
serve/sql/init.sql

@@ -0,0 +1,31 @@
+-- 创建一个名为bi的数据库
+create database if not exists bi;
+-- 切换到bi数据库
+use bi;
+
+-- 创建一个名为users的表,用于存储用户信息
+CREATE TABLE  if NOT EXISTS `user` (
+                         `id` BIGINT NOT NULL  PRIMARY  KEY COMMENT '用户ID',
+                         `user_account` VARCHAR(255) NOT NULL COMMENT '用户账号',
+                         `user_password` VARCHAR(255) NOT NULL COMMENT '用户密码',
+                         `user_name` VARCHAR(255) NOT NULL COMMENT '用户名称',
+                         `user_avatar` VARCHAR(1024) DEFAULT NULL COMMENT '用户头像',
+                         `user_role` CHAR(32) NOT NULL DEFAULT '用户' COMMENT '用户角色',
+                         `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                         `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                         `delete_flag` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '删除标志,0:未删除,1:已删除'
+) COMMENT='用户表';
+
+-- 创建一个名为charts的表,用于存储图表信息
+CREATE TABLE if NOT EXISTS `chart` (
+                          `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '图表ID',
+                          `analysis_target` TEXT NOT NULL COMMENT '分析目标',
+                          `chart_data` TEXT NOT NULL COMMENT '图标数据',
+                          `chart_type` VARCHAR(255) NOT NULL COMMENT '图标类型',
+                          `generated_chart_data` TEXT COMMENT '生成的图表数据',
+                          `analysis_conclusion` TEXT COMMENT '生成的分析结论',
+                          `user_id` BIGINT NOT NULL COMMENT '创建用户ID',
+                          `created_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                          `updated_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                          `delete_flag` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '删除标志,0:未删除,1:已删除'
+) COMMENT='图标表';

+ 18 - 0
serve/src/main/java/space/anyi/BI/BIApplication.java

@@ -0,0 +1,18 @@
+package space.anyi.BI;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @ProjectName: BI
+ * @FileName: BIApplication
+ * @Author: 杨逸
+ * @Data:2024/11/28 11:13
+ * @Description:
+ */
+@SpringBootApplication
+public class BIApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(BIApplication.class, args);
+    }
+}

+ 27 - 0
serve/src/main/java/space/anyi/BI/config/MybatisplusConfig.java

@@ -0,0 +1,27 @@
+package space.anyi.BI.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @ProjectName: BI
+ * @FileName: MybatisplusConfig
+ * @Author: 杨逸
+ * @Data:2024/11/28 19:14
+ * @Description:
+ */
+@Configuration
+@MapperScan("space.anyi.BI.mapper")
+public class MybatisplusConfig {
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor(){
+        //拦截器
+        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
+        //分页拦截器
+        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+        return mybatisPlusInterceptor;
+    }
+}

+ 87 - 0
serve/src/main/java/space/anyi/BI/config/SecurityConfig.java

@@ -0,0 +1,87 @@
+package space.anyi.BI.config;
+
+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.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import space.anyi.BI.filter.JWTAuthenticationTokenFilter;
+
+import javax.annotation.Resource;
+
+/**
+ * @ProjectName: BI
+ * @FileName: SecurityConfig
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:02
+ * @Description:
+ */
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@Configuration
+public class SecurityConfig  extends WebSecurityConfigurerAdapter {
+    @Resource
+    private JWTAuthenticationTokenFilter jwtAuthenticationTokenFilter;
+    @Resource
+    private  AccessDeniedHandler accessDeniedHandler;
+    @Resource
+    private  AuthenticationEntryPoint authenticationEntryPoint;
+    /**
+     * 密码校验方式
+     * @return
+     */
+    @Bean
+    public PasswordEncoder passwordEncoder(){
+        return new BCryptPasswordEncoder();
+    }
+
+    /**
+     * 安全配置
+     * @param http
+     * @throws Exception
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http
+                //关闭csrf
+                .csrf().disable()
+                //不通过Session获取SecurityContext
+                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+                .and()
+                .authorizeRequests()
+                // 对于登录接口 允许匿名访问
+                .antMatchers(HttpMethod.POST,"/user/login","/user/register").anonymous()
+                .antMatchers(HttpMethod.GET,"/swagger/**","/v2/**","/v2/api-docs-ext/**","/swagger-resources/**").anonymous()
+                //放行对静态资源的访问
+                .antMatchers(HttpMethod.GET,"/","/*.html","/**/*.css","/**/*.js","/img/**","/fonts/**").permitAll()
+                .antMatchers(HttpMethod.GET,"/user/getVerifyCode","/book/getBookList","/book/*").permitAll()
+                .antMatchers(HttpMethod.POST,"/user/logout","/user/updatePassword").authenticated()
+                .antMatchers(HttpMethod.POST,"/uploadImage").permitAll()
+                // 除上面外的所有请求全部需要认证访问
+                .anyRequest().authenticated();
+
+        //关闭Security默认的退出接口
+        http.logout().disable();
+        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+
+        http.exceptionHandling()
+                .accessDeniedHandler(accessDeniedHandler)
+                .authenticationEntryPoint(authenticationEntryPoint);
+        //允许跨域
+        http.cors();
+    }
+
+
+    @Override
+    @Bean
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+}

+ 67 - 0
serve/src/main/java/space/anyi/BI/config/SwaggerConfig.java

@@ -0,0 +1,67 @@
+package space.anyi.BI.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+/**
+ * @ProjectName: BI
+ * @FileName: SwaggerConfig
+ * @Author: 杨逸
+ * @Data:2024/11/28 11:37
+ * @Description:
+ */
+@Configuration
+@EnableSwagger2WebMvc
+public class SwaggerConfig {
+    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)  // DocumentationType.SWAGGER_2 固定的,代表swagger2
+                .groupName("开发环境") // 如果配置多个文档的时候,那么需要配置groupName来分组标识
+                .apiInfo(apiInfo()) // 用于生成API信息
+                .select() // select()函数返回一个ApiSelectorBuilder实例,用来控制接口被swagger做成文档
+                // 扫描指定包下的接口,最为常用
+                .apis(RequestHandlerSelectors.basePackage("space.anyi.BI.controller"))
+                //.withClassAnnotation(RestController.class) // 扫描带有指定注解的类下所有接口
+                //.withMethodAnnotation(PostMapping.class) // 扫描带有指定注解的方法接口
+                //.apis(RequestHandlerSelectors.any()) // 扫描所有
+
+                // 选择所有的API,如果你想只为部分API生成文档,可以配置这里
+                .paths(PathSelectors.any()
+                        //.any() // 满足条件的路径,该断言总为true
+                        //.none() // 不满足条件的路径,该断言总为false(可用于生成环境屏蔽 swagger)
+                        //.ant("/user/**") // 满足字符串表达式路径
+                        //.regex("") // 符合正则的路径
+                )
+                .build();
+    }
+
+    /**
+     * 用于定义API主界面的信息,比如可以声明所有的API的总标题、描述、版本
+     * @return
+     */
+    private ApiInfo apiInfo() {
+
+        Contact contact = new Contact(
+                "杨逸", // 作者姓名
+                "http://anyi.space/", // 作者网址
+                "123456789@163.com"); // 作者邮箱
+
+        return new ApiInfoBuilder()
+                .title("BI项目API") //  可以用来自定义API的主标题
+                .description("BI项目SwaggerAPI管理") // 可以用来描述整体的API
+                //.termsOfServiceUrl("https://www.baidu.com") // 用于定义服务的域名(跳转链接)
+                .version("1.0") // 可以用来定义版本
+                //.license("Swagger-的使用教程")
+                //.licenseUrl("https://blog.csdn.net")
+                .contact(contact)
+                .build(); //
+    }
+}

+ 82 - 0
serve/src/main/java/space/anyi/BI/constant/SystemConstants.java

@@ -0,0 +1,82 @@
+package space.anyi.BI.constant;
+
+/**
+ * @ProjectName: BI
+ * @FileName: SystemConstants
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:16
+ * @Description:
+ */
+public class SystemConstants {
+
+
+    /**
+     * 用户可用
+     */
+    public static final String USER_STATUS_NORMAL = "1";
+
+    /**
+     *redis中文章阅读量key的前缀
+     */
+    public static final String ARTICLE_VIEW_COUNT_KEY = "article:viewCount";
+
+    /**
+     * .png图片类型
+     */
+    public static final String IMAGE_TYPE_PNG = ".png";
+
+    /**
+     * .jpg图片类型
+     */
+    public static final String IMAGE_TYPE_JPG = ".jpg";
+
+    /**
+     * 管理员类型用户
+     */
+    public static final String ADMIN = "admin";
+
+    /**
+     * 存入redis用户ID的前缀
+     */
+    public static final String LOGIN_USER_REDIS_KEY = "login:user:";
+
+    public static final String ROLE_STATUS_NORMAL = "0";
+    public static final String MENU_STATUS_NORMAL = "0";
+    public static final String MENU_TYPE_BUTTON = "F";
+    /**
+     * 存入redis的验证码key前缀
+     */
+    public static final String VERIFY_CODE_KEY = "verify:key:";
+    /**
+     * png格式图片的base64前缀
+     */
+    public static final String IMAGE_PNG_BASE64 = "data:image/png;base64,";
+    /**
+     * jpg格式图片的base64前缀
+     */
+    public static final String IMAGE_JPG_BASE64 = "data:image/png;base64,";
+    /**
+     *  书籍状态:已借出
+     */
+    public static final String BOOK_STATE_BORROWED = "1";
+    /**
+     * 书籍状态:未借出
+     */
+    public static final String BOOK_STATE_NORMAL = "0";
+    /**
+     * 借阅记录状态:未归还
+     */
+    public static final String BORROW_RECORD_STATE_NOT_RETURN = "0";
+    /**
+     * 借阅记录状态:已归还
+     */
+    public static final String BORROW_RECORD_STATE_RETURN = "1";
+    /**
+     * 内容类型XLS
+     */
+    public static final String CONTENT_TYPE_XLS = "application/vnd.ms-excel";
+    /**
+     *  内容类型XLSX
+     */
+    public static final String CONTENT_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+}

+ 60 - 0
serve/src/main/java/space/anyi/BI/controller/ChartController.java

@@ -0,0 +1,60 @@
+package space.anyi.BI.controller;
+
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import space.anyi.BI.entity.Chart;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.service.ChartService;
+import space.anyi.BI.util.SecurityUtils;
+
+import javax.annotation.Resource;
+
+/**
+ * @ProjectName: BI
+ * @FileName: ChartController
+ * @Author: 杨逸
+ * @Data:2024/11/28 19:53
+ * @Description:
+ */
+@Controller()
+@RequestMapping("/chart")
+public class ChartController {
+    @Resource
+    private ChartService chartService;
+    @GetMapping("/getChartById/{id}")
+    @ResponseBody
+    public ResponseResult getChartById(@PathVariable("id") Long id){
+        Chart chart = chartService.getById(id);
+        return ResponseResult.okResult(chart);
+    }
+    @PostMapping("/add")
+    @ResponseBody
+    public ResponseResult addChart(@RequestBody Chart chart){
+        long id = IdUtil.getSnowflake(1, 1).nextId();
+        chart.setId(id);
+        Long userId = SecurityUtils.getUserId();
+        chart.setUserId(userId);
+        chartService.save(chart);
+        return ResponseResult.okResult();
+    }
+    @DeleteMapping("/delete/{id}")
+    @ResponseBody
+    public ResponseResult deleteChartById(@PathVariable Long id){
+        chartService.removeById(id);
+        return ResponseResult.okResult();
+    }
+    @PutMapping("/update")
+    @ResponseBody
+    public ResponseResult updateChart(@RequestBody Chart chart){
+        chartService.updateById(chart);
+        return ResponseResult.okResult();
+    }
+    @GetMapping("/list")
+    @ResponseBody
+    public ResponseResult page(Integer size,Integer current){
+        Page<Chart> page = chartService.page(new Page<>(current, size));
+        return ResponseResult.okResult(page);
+    }
+}

+ 36 - 0
serve/src/main/java/space/anyi/BI/controller/UserController.java

@@ -0,0 +1,36 @@
+package space.anyi.BI.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.entity.User;
+import space.anyi.BI.service.UserService;
+
+import javax.annotation.Resource;
+
+/**
+ * @ProjectName: BI
+ * @FileName: UserController
+ * @Author: 杨逸
+ * @Data:2024/11/28 19:52
+ * @Description:
+ */
+@Controller()
+@RequestMapping("/user")
+public class UserController {
+    @Resource
+    private UserService userService;
+    @PostMapping("/login")
+    @ResponseBody
+    public ResponseResult login(@RequestBody User user){
+        return userService.login(user);
+    }
+    @PostMapping("/register")
+    @ResponseBody
+    public ResponseResult resister(@RequestBody User user){
+        return userService.register(user);
+    }
+}

+ 271 - 0
serve/src/main/java/space/anyi/BI/entity/Chart.java

@@ -0,0 +1,271 @@
+package space.anyi.BI.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 图标表
+ * @TableName chart
+ */
+@TableName(value ="chart")
+public class Chart implements Serializable {
+    /**
+     * 图表ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 分析目标
+     */
+    private String analysisTarget;
+
+    /**
+     * 图标数据
+     */
+    private String chartData;
+
+    /**
+     * 图标类型
+     */
+    private String chartType;
+
+    /**
+     * 生成的图表数据
+     */
+    private String generatedChartData;
+
+    /**
+     * 生成的分析结论
+     */
+    private String analysisConclusion;
+
+    /**
+     * 创建用户ID
+     */
+    private Long userId;
+
+    /**
+     * 创建时间
+     */
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updatedTime;
+
+    /**
+     * 删除标志,0:未删除,1:已删除
+     */
+    private Integer deleteFlag;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 图表ID
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * 图表ID
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * 分析目标
+     */
+    public String getAnalysisTarget() {
+        return analysisTarget;
+    }
+
+    /**
+     * 分析目标
+     */
+    public void setAnalysisTarget(String analysisTarget) {
+        this.analysisTarget = analysisTarget;
+    }
+
+    /**
+     * 图标数据
+     */
+    public String getChartData() {
+        return chartData;
+    }
+
+    /**
+     * 图标数据
+     */
+    public void setChartData(String chartData) {
+        this.chartData = chartData;
+    }
+
+    /**
+     * 图标类型
+     */
+    public String getChartType() {
+        return chartType;
+    }
+
+    /**
+     * 图标类型
+     */
+    public void setChartType(String chartType) {
+        this.chartType = chartType;
+    }
+
+    /**
+     * 生成的图表数据
+     */
+    public String getGeneratedChartData() {
+        return generatedChartData;
+    }
+
+    /**
+     * 生成的图表数据
+     */
+    public void setGeneratedChartData(String generatedChartData) {
+        this.generatedChartData = generatedChartData;
+    }
+
+    /**
+     * 生成的分析结论
+     */
+    public String getAnalysisConclusion() {
+        return analysisConclusion;
+    }
+
+    /**
+     * 生成的分析结论
+     */
+    public void setAnalysisConclusion(String analysisConclusion) {
+        this.analysisConclusion = analysisConclusion;
+    }
+
+    /**
+     * 创建用户ID
+     */
+    public Long getUserId() {
+        return userId;
+    }
+
+    /**
+     * 创建用户ID
+     */
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * 创建时间
+     */
+    public Date getCreatedTime() {
+        return createdTime;
+    }
+
+    /**
+     * 创建时间
+     */
+    public void setCreatedTime(Date createdTime) {
+        this.createdTime = createdTime;
+    }
+
+    /**
+     * 更新时间
+     */
+    public Date getUpdatedTime() {
+        return updatedTime;
+    }
+
+    /**
+     * 更新时间
+     */
+    public void setUpdatedTime(Date updatedTime) {
+        this.updatedTime = updatedTime;
+    }
+
+    /**
+     * 删除标志,0:未删除,1:已删除
+     */
+    public Integer getDeleteFlag() {
+        return deleteFlag;
+    }
+
+    /**
+     * 删除标志,0:未删除,1:已删除
+     */
+    public void setDeleteFlag(Integer deleteFlag) {
+        this.deleteFlag = deleteFlag;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Chart other = (Chart) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getAnalysisTarget() == null ? other.getAnalysisTarget() == null : this.getAnalysisTarget().equals(other.getAnalysisTarget()))
+            && (this.getChartData() == null ? other.getChartData() == null : this.getChartData().equals(other.getChartData()))
+            && (this.getChartType() == null ? other.getChartType() == null : this.getChartType().equals(other.getChartType()))
+            && (this.getGeneratedChartData() == null ? other.getGeneratedChartData() == null : this.getGeneratedChartData().equals(other.getGeneratedChartData()))
+            && (this.getAnalysisConclusion() == null ? other.getAnalysisConclusion() == null : this.getAnalysisConclusion().equals(other.getAnalysisConclusion()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getCreatedTime() == null ? other.getCreatedTime() == null : this.getCreatedTime().equals(other.getCreatedTime()))
+            && (this.getUpdatedTime() == null ? other.getUpdatedTime() == null : this.getUpdatedTime().equals(other.getUpdatedTime()))
+            && (this.getDeleteFlag() == null ? other.getDeleteFlag() == null : this.getDeleteFlag().equals(other.getDeleteFlag()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getAnalysisTarget() == null) ? 0 : getAnalysisTarget().hashCode());
+        result = prime * result + ((getChartData() == null) ? 0 : getChartData().hashCode());
+        result = prime * result + ((getChartType() == null) ? 0 : getChartType().hashCode());
+        result = prime * result + ((getGeneratedChartData() == null) ? 0 : getGeneratedChartData().hashCode());
+        result = prime * result + ((getAnalysisConclusion() == null) ? 0 : getAnalysisConclusion().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getCreatedTime() == null) ? 0 : getCreatedTime().hashCode());
+        result = prime * result + ((getUpdatedTime() == null) ? 0 : getUpdatedTime().hashCode());
+        result = prime * result + ((getDeleteFlag() == null) ? 0 : getDeleteFlag().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", analysisTarget=").append(analysisTarget);
+        sb.append(", chartData=").append(chartData);
+        sb.append(", chartType=").append(chartType);
+        sb.append(", generatedChartData=").append(generatedChartData);
+        sb.append(", analysisConclusion=").append(analysisConclusion);
+        sb.append(", createdBy=").append(userId);
+        sb.append(", createdAt=").append(createdTime);
+        sb.append(", updatedAt=").append(updatedTime);
+        sb.append(", deleteFlag=").append(deleteFlag);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 65 - 0
serve/src/main/java/space/anyi/BI/entity/LoginUserDetails.java

@@ -0,0 +1,65 @@
+package space.anyi.BI.entity;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+
+/**
+ * @ProjectName: BI
+ * @FileName: LoginUserDetails
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:14
+ * @Description:
+ */
+public class LoginUserDetails implements UserDetails {
+
+    private User user;
+
+    public LoginUserDetails(User user) {
+        this.user = user;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return null;
+    }
+
+    @Override
+    public String getPassword() {
+        return user.getUserPassword();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUserAccount();
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+}

+ 181 - 0
serve/src/main/java/space/anyi/BI/entity/ResponseResult.java

@@ -0,0 +1,181 @@
+package space.anyi.BI.entity;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.io.Serializable;
+
+/**
+ * @Projectname: gditSpringBootLibrary
+ * @Filename: ResponseResult
+ * @Author: 杨逸
+ * @Data:2023/12/14 17:48
+ * @Description: 统一响应实体
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ResponseResult<T> implements Serializable {
+    private Integer code;
+    private String msg;
+    private T data;
+
+    public ResponseResult() {
+        this.code = AppHttpCodeEnum.SUCCESS.getCode();
+        this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
+    }
+
+    public ResponseResult(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg, T data) {
+        this.code = code;
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public static ResponseResult errorResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.error(code, msg);
+    }
+    public static ResponseResult okResult() {
+        ResponseResult result = new ResponseResult();
+        return result;
+    }
+    public static ResponseResult okResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.ok(code, null, msg);
+    }
+
+    public static ResponseResult okResult(Object data) {
+        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
+        if(data!=null) {
+            result.setData(data);
+        }
+        return result;
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums){
+        return setAppHttpCodeEnum(enums,enums.getMsg());
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
+        return setAppHttpCodeEnum(enums,msg);
+    }
+
+    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
+        return okResult(enums.getCode(),enums.getMsg());
+    }
+
+    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
+        return okResult(enums.getCode(),msg);
+    }
+
+    public ResponseResult<?> error(Integer code, String msg) {
+        this.code = code;
+        this.msg = msg;
+        return this;
+    }
+
+    public ResponseResult<?> ok(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+        return this;
+    }
+
+    public ResponseResult<?> ok(Integer code, T data, String msg) {
+        this.code = code;
+        this.data = data;
+        this.msg = msg;
+        return this;
+    }
+
+    public ResponseResult<?> ok(T data) {
+        this.data = data;
+        return this;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    /**
+     * 内部类,响应状态枚举
+     */
+    public enum AppHttpCodeEnum {
+        // 成功
+        SUCCESS(200,"操作成功"),
+        // 登录
+        NEED_LOGIN(401,"需要登录后操作"),
+        NO_OPERATOR_AUTH(403,"无权限操作"),
+        BOOK_IS_BORROWED(410, "图书已借出"),
+        SYSTEM_ERROR(500,"出现错误"),
+        USERNAME_EXIST(501,"用户名已存在"),
+        PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"),
+        REQUIRE_USERNAME(504, "必需填写用户名"),
+        LOGIN_ERROR(505,"用户名或密码错误"),
+        ARTICLE_NOT_EXIST(506,"文章不存在"),
+        COMMENT_CONTENT_NOT_NULL(507,"评论不能为空"),
+        FILE_NOT_NULL(508,"文件不能为空"),
+        FILE_TYPE_ERROR(509,"文件类型错误"),
+        USER_NOT_NULL(510,"用户不能为空"),
+        PASSWORD_NOT_NULL(511,"密码不能为空"),
+        EMAIL_NOT_NULL(512,"邮箱不能为空"),
+        MENU_HAS_CHILDREN(513, "存在子菜单,不允许删除"),
+        PASSWORD_ERROR(514, "密码错误"),
+        USER_NOT_EXIST(515, "用户不存在"),
+        PASSWORD_SAME(516, "新密码不能与旧密码一样"),
+        VERIFY_CODE_ERROR(517, "验证码错误"),
+        USER_STATE_ERROR(518, "用户状态值不正确"),
+        User_ID_NOT_NULL(519,"用户id不能为空" ),
+        BOOK_ISBN_EXIST(520, "图书ISBN已存在"),
+        BOOK_EXPORT_ERROR(521,"导出图书失败"),
+        USER_EXPORT_ERROR(522, "导出用户失败"),
+        BORROW_RECORD_EXPORT_ERROR(523,"导出借阅记录失败" ),
+        BOOK_IMPORT_ERROR(524,"导入图书数据失败" ),
+        USER_FACE_INFO_ERROR(525, "更新人脸信息失败"),
+        FACE_LOGIN_ERROR(526, "人脸登陆失败,对比不成功"),
+        USER_NOT_ACTIVE_FACE(527, "未激活人脸登陆");
+
+        private int code;
+        private String msg;
+
+        AppHttpCodeEnum(int code, String errorMessage){
+            this.code = code;
+            this.msg = errorMessage;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+    }
+}
+

+ 249 - 0
serve/src/main/java/space/anyi/BI/entity/User.java

@@ -0,0 +1,249 @@
+package space.anyi.BI.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 用户表
+ * @TableName user
+ */
+@TableName(value ="user")
+public class User implements Serializable {
+    /**
+     * 用户ID
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 用户账号
+     */
+    private String userAccount;
+
+    /**
+     * 用户密码
+     */
+    private String userPassword;
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 用户头像
+     */
+    private String userAvatar;
+
+    /**
+     * 用户角色
+     */
+    private String userRole = "用户";
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    /**
+     * 删除标志,0:未删除,1:已删除
+     */
+    private Integer deleteFlag;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * 用户ID
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * 用户账号
+     */
+    public String getUserAccount() {
+        return userAccount;
+    }
+
+    /**
+     * 用户账号
+     */
+    public void setUserAccount(String userAccount) {
+        this.userAccount = userAccount;
+    }
+
+    /**
+     * 用户密码
+     */
+    public String getUserPassword() {
+        return userPassword;
+    }
+
+    /**
+     * 用户密码
+     */
+    public void setUserPassword(String userPassword) {
+        this.userPassword = userPassword;
+    }
+
+    /**
+     * 用户名称
+     */
+    public String getUserName() {
+        return userName;
+    }
+
+    /**
+     * 用户名称
+     */
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    /**
+     * 用户头像
+     */
+    public String getUserAvatar() {
+        return userAvatar;
+    }
+
+    /**
+     * 用户头像
+     */
+    public void setUserAvatar(String userAvatar) {
+        this.userAvatar = userAvatar;
+    }
+
+    /**
+     * 用户角色
+     */
+    public String getUserRole() {
+        return userRole;
+    }
+
+    /**
+     * 用户角色
+     */
+    public void setUserRole(String userRole) {
+        this.userRole = userRole;
+    }
+
+    /**
+     * 创建时间
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * 创建时间
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * 更新时间
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * 更新时间
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
+     * 删除标志,0:未删除,1:已删除
+     */
+    public Integer getDeleteFlag() {
+        return deleteFlag;
+    }
+
+    /**
+     * 删除标志,0:未删除,1:已删除
+     */
+    public void setDeleteFlag(Integer deleteFlag) {
+        this.deleteFlag = deleteFlag;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        User other = (User) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getUserAccount() == null ? other.getUserAccount() == null : this.getUserAccount().equals(other.getUserAccount()))
+            && (this.getUserPassword() == null ? other.getUserPassword() == null : this.getUserPassword().equals(other.getUserPassword()))
+            && (this.getUserName() == null ? other.getUserName() == null : this.getUserName().equals(other.getUserName()))
+            && (this.getUserAvatar() == null ? other.getUserAvatar() == null : this.getUserAvatar().equals(other.getUserAvatar()))
+            && (this.getUserRole() == null ? other.getUserRole() == null : this.getUserRole().equals(other.getUserRole()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()))
+            && (this.getDeleteFlag() == null ? other.getDeleteFlag() == null : this.getDeleteFlag().equals(other.getDeleteFlag()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getUserAccount() == null) ? 0 : getUserAccount().hashCode());
+        result = prime * result + ((getUserPassword() == null) ? 0 : getUserPassword().hashCode());
+        result = prime * result + ((getUserName() == null) ? 0 : getUserName().hashCode());
+        result = prime * result + ((getUserAvatar() == null) ? 0 : getUserAvatar().hashCode());
+        result = prime * result + ((getUserRole() == null) ? 0 : getUserRole().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        result = prime * result + ((getDeleteFlag() == null) ? 0 : getDeleteFlag().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", userAccount=").append(userAccount);
+        sb.append(", userPassword=").append(userPassword);
+        sb.append(", userName=").append(userName);
+        sb.append(", userAvatar=").append(userAvatar);
+        sb.append(", userRole=").append(userRole);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", deleteFlag=").append(deleteFlag);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 75 - 0
serve/src/main/java/space/anyi/BI/entity/vo/UserInfo.java

@@ -0,0 +1,75 @@
+package space.anyi.BI.entity.vo;
+
+
+/**
+ * @ProjectName: BI
+ * @FileName: UserInfo
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:36
+ * @Description:
+ */
+public class UserInfo {
+
+
+    /**
+     * 用户账号
+     */
+    private String userAccount;
+
+
+    /**
+     * 用户名称
+     */
+    private String userName;
+
+    /**
+     * 用户头像
+     */
+    private String userAvatar;
+
+    /**
+     * 用户角色
+     */
+    private String userRole;
+
+    public UserInfo(String userAccount, String userName, String userAvatar, String userRole) {
+        this.userAccount = userAccount;
+        this.userName = userName;
+        this.userAvatar = userAvatar;
+        this.userRole = userRole;
+    }
+
+    public String getUserAccount() {
+        return userAccount;
+    }
+
+    public void setUserAccount(String userAccount) {
+        this.userAccount = userAccount;
+    }
+
+
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getUserAvatar() {
+        return userAvatar;
+    }
+
+    public void setUserAvatar(String userAvatar) {
+        this.userAvatar = userAvatar;
+    }
+
+    public String getUserRole() {
+        return userRole;
+    }
+
+    public void setUserRole(String userRole) {
+        this.userRole = userRole;
+    }
+}

+ 31 - 0
serve/src/main/java/space/anyi/BI/exception/SystemException.java

@@ -0,0 +1,31 @@
+package space.anyi.BI.exception;
+
+import space.anyi.BI.entity.ResponseResult;
+
+/**
+ * @ProjectName: BI
+ * @FileName: SystemException
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:22
+ * @Description: TODO
+ */
+public class SystemException extends RuntimeException {
+    private int code;
+
+    private String msg;
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public SystemException(ResponseResult.AppHttpCodeEnum httpCodeEnum) {
+        super(httpCodeEnum.getMsg());
+        this.code = httpCodeEnum.getCode();
+        this.msg = httpCodeEnum.getMsg();
+    }
+
+}

+ 76 - 0
serve/src/main/java/space/anyi/BI/filter/JWTAuthenticationTokenFilter.java

@@ -0,0 +1,76 @@
+package space.anyi.BI.filter;
+
+import com.alibaba.fastjson.JSON;
+import io.jsonwebtoken.Claims;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+import space.anyi.BI.constant.SystemConstants;
+import space.anyi.BI.entity.LoginUserDetails;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.util.JwtUtil;
+import space.anyi.BI.util.RedisCache;
+import space.anyi.BI.util.WebUtils;
+
+import javax.annotation.Resource;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * @ProjectName: BI
+ * @FileName: JWTAuthenticationTokenFilter
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:04
+ * @Description:
+ */
+@Component
+public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
+    @Resource
+    private RedisCache redisCache;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+        //获取token
+        System.out.println("httpServletRequest.getRequestURI() = " + httpServletRequest.getRequestURI());
+        String token = httpServletRequest.getHeader("token");
+        System.out.println("token = " + token);
+        if (!StringUtils.hasText(token)) {
+            filterChain.doFilter(httpServletRequest, httpServletResponse);
+            return;
+        }
+        //解析token
+        Claims jwt;
+        try {
+            jwt = JwtUtil.parseJWT(token);
+        } catch (Exception e) {
+            e.printStackTrace();
+            //响应前端,需要登陆
+            ResponseResult errorResult = ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.NEED_LOGIN);
+            WebUtils.renderString(httpServletResponse, JSON.toJSONString(errorResult));
+            return;
+        }
+        //从redis中获取用户信息
+        LoginUserDetails loginUserDetails = redisCache.getCacheObject(SystemConstants.LOGIN_USER_REDIS_KEY +jwt.getSubject());
+
+        //已退出登陆或登陆已过期
+        if(Objects.isNull(loginUserDetails)){
+            //响应前端,需要登陆
+            ResponseResult errorResult = ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.NEED_LOGIN);
+            WebUtils.renderString(httpServletResponse, JSON.toJSONString(errorResult));
+            return;
+        }
+
+        //存入SecurityContextHolder
+        Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginUserDetails,null,null);
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        //放行
+        filterChain.doFilter(httpServletRequest,httpServletResponse);
+    }
+}

+ 30 - 0
serve/src/main/java/space/anyi/BI/handler/exception/GlobalExceptionHandler.java

@@ -0,0 +1,30 @@
+package space.anyi.BI.handler.exception;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.exception.SystemException;
+
+/**
+ * @ProjectName: BI
+ * @FileName: GlobalExceptionHandler
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:25
+ * @Description:
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler{
+    private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+    @ExceptionHandler(SystemException.class)
+    public ResponseResult systemException(SystemException e){
+        log.error("发生错误:{}",e);
+        return ResponseResult.errorResult(e.getCode(),e.getMsg());
+    }
+    @ExceptionHandler(Exception.class)
+    public ResponseResult systemException(Exception e){
+        log.error("发生错误:{}",e);
+        return ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage());
+    }
+}

+ 37 - 0
serve/src/main/java/space/anyi/BI/handler/mybatispuls/MyMetaObjectHandler.java

@@ -0,0 +1,37 @@
+package space.anyi.BI.handler.mybatispuls;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+import space.anyi.BI.util.SecurityUtils;
+
+import java.util.Date;
+
+/**
+ * @ProjectName: BI
+ * @FileName: MyMetaObjectHandler
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:41
+ * @Description:
+ */
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        Long userId = null;
+        try {
+            userId = SecurityUtils.getUserId();
+        } catch (Exception e) {
+            e.printStackTrace();
+            userId = -1L;//表示是自己创建
+        }
+        System.out.println("插入拦截器触发");
+        this.setFieldValByName("createTime", new Date(), metaObject);
+        this.setFieldValByName("updateTime", new Date(), metaObject);
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        this.setFieldValByName("updateTime", new Date(), metaObject);
+    }
+}

+ 30 - 0
serve/src/main/java/space/anyi/BI/handler/security/AccessDeniedHandlerImpl.java

@@ -0,0 +1,30 @@
+package space.anyi.BI.handler.security;
+
+import com.alibaba.fastjson.JSON;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.util.WebUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @ProjectName: BI
+ * @FileName: AccessDeniedHandlerImpl
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:17
+ * @Description:
+ */
+@Component
+public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
+    @Override
+    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
+        e.printStackTrace();
+        ResponseResult responseResult = ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.NO_OPERATOR_AUTH);
+        WebUtils.renderString(httpServletResponse, JSON.toJSONString(responseResult));
+    }
+}

+ 40 - 0
serve/src/main/java/space/anyi/BI/handler/security/AuthenticationEntryPointImpl.java

@@ -0,0 +1,40 @@
+package space.anyi.BI.handler.security;
+
+import com.alibaba.fastjson.JSON;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.util.WebUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @ProjectName: BI
+ * @FileName: AuthenticationEntryPointImpl
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:18
+ * @Description:
+ */
+@Component
+public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
+    @Override
+    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
+        e.printStackTrace();
+        ResponseResult responseResult = null;
+        //针对不同异常,返回对应的信息
+        if (e instanceof InsufficientAuthenticationException) {
+            responseResult = ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.NO_OPERATOR_AUTH);
+        } else if (e instanceof InternalAuthenticationServiceException) {
+            responseResult = ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.LOGIN_ERROR);
+        } else {
+            responseResult = ResponseResult.errorResult(ResponseResult.AppHttpCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage());
+        }
+        WebUtils.renderString(httpServletResponse, JSON.toJSONString(responseResult));
+    }
+}

+ 18 - 0
serve/src/main/java/space/anyi/BI/mapper/ChartMapper.java

@@ -0,0 +1,18 @@
+package space.anyi.BI.mapper;
+
+import space.anyi.BI.entity.Chart;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 杨逸
+* @description 针对表【chart(图标表)】的数据库操作Mapper
+* @createDate 2024-11-28 19:51:56
+* @Entity space.anyi.BI.entity.Chart
+*/
+public interface ChartMapper extends BaseMapper<Chart> {
+
+}
+
+
+
+

+ 18 - 0
serve/src/main/java/space/anyi/BI/mapper/UserMapper.java

@@ -0,0 +1,18 @@
+package space.anyi.BI.mapper;
+
+import space.anyi.BI.entity.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 杨逸
+* @description 针对表【user(用户表)】的数据库操作Mapper
+* @createDate 2024-11-28 19:46:42
+* @Entity space.anyi.BI.entity.User
+*/
+public interface UserMapper extends BaseMapper<User> {
+
+}
+
+
+
+

+ 13 - 0
serve/src/main/java/space/anyi/BI/service/ChartService.java

@@ -0,0 +1,13 @@
+package space.anyi.BI.service;
+
+import space.anyi.BI.entity.Chart;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 杨逸
+* @description 针对表【chart(图标表)】的数据库操作Service
+* @createDate 2024-11-28 19:51:56
+*/
+public interface ChartService extends IService<Chart> {
+
+}

+ 17 - 0
serve/src/main/java/space/anyi/BI/service/UserService.java

@@ -0,0 +1,17 @@
+package space.anyi.BI.service;
+
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.entity.User;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 杨逸
+* @description 针对表【user(用户表)】的数据库操作Service
+* @createDate 2024-11-28 19:46:42
+*/
+public interface UserService extends IService<User> {
+
+    ResponseResult login(User user);
+
+    ResponseResult register(User user);
+}

+ 22 - 0
serve/src/main/java/space/anyi/BI/service/impl/ChartServiceImpl.java

@@ -0,0 +1,22 @@
+package space.anyi.BI.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import space.anyi.BI.entity.Chart;
+import space.anyi.BI.service.ChartService;
+import space.anyi.BI.mapper.ChartMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 杨逸
+* @description 针对表【chart(图标表)】的数据库操作Service实现
+* @createDate 2024-11-28 19:51:56
+*/
+@Service
+public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
+    implements ChartService{
+
+}
+
+
+
+

+ 41 - 0
serve/src/main/java/space/anyi/BI/service/impl/UserDetailsServiceImpl.java

@@ -0,0 +1,41 @@
+package space.anyi.BI.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+import space.anyi.BI.entity.LoginUserDetails;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.entity.User;
+import space.anyi.BI.exception.SystemException;
+import space.anyi.BI.mapper.UserMapper;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+/**
+ * @ProjectName: BI
+ * @FileName: UserDetailsServiceImpl
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:20
+ * @Description:
+ */
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+    @Resource
+    private UserMapper userMapper;
+    @Override
+    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
+        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper
+                .eq(User::getUserAccount, s);
+                //.eq(User::getUserState, SystemConstants.USER_STATUS_NORMAL);
+        User user = userMapper.selectOne(queryWrapper);
+        if (Objects.isNull(user)) {
+            throw new SystemException(ResponseResult.AppHttpCodeEnum.LOGIN_ERROR);
+        }
+        LoginUserDetails loginUserDetails = new LoginUserDetails(user);
+        return loginUserDetails;
+    }
+}

+ 95 - 0
serve/src/main/java/space/anyi/BI/service/impl/UserServiceImpl.java

@@ -0,0 +1,95 @@
+package space.anyi.BI.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import space.anyi.BI.constant.SystemConstants;
+import space.anyi.BI.entity.LoginUserDetails;
+import space.anyi.BI.entity.ResponseResult;
+import space.anyi.BI.entity.User;
+import space.anyi.BI.entity.vo.UserInfo;
+import space.anyi.BI.exception.SystemException;
+import space.anyi.BI.service.UserService;
+import space.anyi.BI.mapper.UserMapper;
+import org.springframework.stereotype.Service;
+import space.anyi.BI.util.JwtUtil;
+import space.anyi.BI.util.RedisCache;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+* @author 杨逸
+* @description 针对表【user(用户表)】的数据库操作Service实现
+* @createDate 2024-11-28 19:46:42
+*/
+@Service
+public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
+    @Resource
+    private  AuthenticationManager authenticationManager;
+    @Resource
+    private  RedisCache redisCache;
+    @Resource
+    private  PasswordEncoder passwordEncoder;
+    @Override
+    public ResponseResult login(User user) {
+        //认证
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserAccount(), user.getUserPassword());
+        Authentication authentication = authenticationManager.authenticate(authenticationToken);
+        if (Objects.isNull(authentication)){
+            throw new SystemException(ResponseResult.AppHttpCodeEnum.LOGIN_ERROR);
+        }
+        //生成token
+        LoginUserDetails principal = (LoginUserDetails) authentication.getPrincipal();
+        Long id = principal.getUser().getId();
+        String jwt = JwtUtil.createJWT(id.toString());
+        //todo:权限
+        //存入redis
+        redisCache.setCacheObject(SystemConstants.LOGIN_USER_REDIS_KEY+id,principal);
+        //返回结果
+        Map<String,Object> map = new HashMap<>();
+        UserInfo userInfo = new UserInfo(principal.getUser().getUserAccount(),principal.getUser().getUserName(),principal.getUser().getUserAvatar(),principal.getUser().getUserRole());
+        map.put("token",jwt);
+        map.put("userInfo",userInfo);
+        return ResponseResult.okResult(map);
+    }
+
+    @Override
+    public ResponseResult register(User user) {
+        //用户账号是否存在
+        if (userAccountExist(user.getUserAccount())){
+            throw new SystemException(ResponseResult.AppHttpCodeEnum.USERNAME_EXIST);
+        }
+        //密码加密
+        user.setUserPassword(passwordEncoder.encode(user.getUserPassword()));
+        //生成ID
+        long id = IdUtil.getSnowflake(1, 1).nextId();
+        user.setId(id);
+        //存入数据库
+        save(user);
+        return ResponseResult.okResult();
+    }
+    /**
+     * 判断用户账号是否已被注册
+     * @param userAccount
+     * @return
+     */
+    private boolean userAccountExist(String userAccount) {
+        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(User::getUserAccount,userAccount);
+        if (count(queryWrapper)>0) {
+            return true;
+        }
+        return false;
+    }
+}
+
+
+
+

+ 36 - 0
serve/src/main/java/space/anyi/BI/util/BeanCopyUtil.java

@@ -0,0 +1,36 @@
+package space.anyi.BI.util;
+
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @ProjectName: BI
+ * @FileName: BeanCopyUtil
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:19
+ * @Description:
+ */
+public class BeanCopyUtil {
+    private BeanCopyUtil(){}
+
+    public static <S,T> T copyBean(S source,Class<T> clazz) {
+        T target = null;
+        try {
+            target = clazz.newInstance();
+            //使用Spring的工具类实现Bean的拷贝,拷贝依靠的是Bean属性的名称
+            BeanUtils.copyProperties(source, target);
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return target;
+    }
+
+    public static <S,T> List<T> copyBean(List<S> sourceList, Class<T> clazz) {
+        return sourceList.stream().map(s -> copyBean(s,clazz)).collect(Collectors.toList());
+    }
+
+}

+ 112 - 0
serve/src/main/java/space/anyi/BI/util/JwtUtil.java

@@ -0,0 +1,112 @@
+package space.anyi.BI.util;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * @ProjectName: BI
+ * @FileName: JwtUtil
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:08
+ * @Description: TODO
+ */
+public class JwtUtil{
+
+    //有效期为
+    public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000  一个小时
+    //设置秘钥明文
+    public static final String JWT_KEY = "yangyi";
+
+    public static String getUUID(){
+        String token = UUID.randomUUID().toString().replaceAll("-", "");
+        return token;
+    }
+
+    /**
+     * 生成jtw
+     * @param subject token中要存放的数据(json格式)
+     * @return
+     */
+    public static String createJWT(String subject) {
+        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
+        return builder.compact();
+    }
+
+    /**
+     * 生成jtw
+     * @param subject token中要存放的数据(json格式)
+     * @param ttlMillis token超时时间
+     * @return
+     */
+    public static String createJWT(String subject, Long ttlMillis) {
+        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
+        return builder.compact();
+    }
+
+    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
+        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+        SecretKey secretKey = generalKey();
+        long nowMillis = System.currentTimeMillis();
+        Date now = new Date(nowMillis);
+        if(ttlMillis==null){
+            ttlMillis=JwtUtil.JWT_TTL;
+        }
+        long expMillis = nowMillis + ttlMillis;
+        Date expDate = new Date(expMillis);
+        return Jwts.builder()
+                .setId(uuid)              //唯一的ID
+                .setSubject(subject)   // 主题  可以是JSON数据
+                .setIssuer("杨逸")     // 签发者
+                .setIssuedAt(now)      // 签发时间
+                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
+                .setExpiration(expDate);
+    }
+
+    /**
+     * 创建token
+     * @param id
+     * @param subject
+     * @param ttlMillis
+     * @return
+     */
+    public static String createJWT(String id, String subject, Long ttlMillis) {
+        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
+        return builder.compact();
+    }
+
+
+
+    /**
+     * 生成加密后的秘钥 secretKey
+     * @return
+     */
+    public static SecretKey generalKey() {
+        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
+        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
+        return key;
+    }
+
+    /**
+     * 解析
+     *
+     * @param jwt
+     * @return
+     * @throws Exception
+     */
+    public static Claims parseJWT(String jwt) throws Exception {
+        SecretKey secretKey = generalKey();
+        return Jwts.parser()
+                .setSigningKey(secretKey)
+                .parseClaimsJws(jwt)
+                .getBody();
+    }
+
+}

+ 252 - 0
serve/src/main/java/space/anyi/BI/util/RedisCache.java

@@ -0,0 +1,252 @@
+package space.anyi.BI.util;
+
+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 javax.annotation.Resource;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @ProjectName: BI
+ * @FileName: RedisCache
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:05
+ * @Description:
+ */
+@Component
+public class RedisCache {
+    @Resource
+    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 缓存键值
+     * @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 long deleteObject(final Collection collection)
+    {
+        return redisTemplate.delete(collection);
+    }
+
+    /**
+     * 缓存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);
+    }
+
+    /**
+     * 自增map中的值
+     * @param key map的key
+     * @param hKey map中的key
+     */
+    public void incrementCacheMapValue(final String key,final String hKey){
+        redisTemplate.opsForHash().increment(key,hKey,1);
+    }
+
+    /**
+     * 获取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
+     * @param hkey
+     */
+    public void delCacheMapValue(final String key, final String hkey)
+    {
+        HashOperations hashOperations = redisTemplate.opsForHash();
+        hashOperations.delete(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);
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern)
+    {
+        return redisTemplate.keys(pattern);
+    }
+}

+ 40 - 0
serve/src/main/java/space/anyi/BI/util/SecurityUtils.java

@@ -0,0 +1,40 @@
+package space.anyi.BI.util;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import space.anyi.BI.entity.LoginUserDetails;
+
+/**
+ * @ProjectName: BI
+ * @FileName: SecurityUtils
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:42
+ * @Description:
+ */
+public class SecurityUtils {
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUserDetails getLoginUser()
+    {
+        return (LoginUserDetails) getAuthentication().getPrincipal();
+    }
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication() {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    public static Boolean isAdmin(){
+        Long id = getLoginUser().getUser().getId();
+        return id != null && 1 == id;
+        //return id != null && 1L == id || 14787164048668L == id;
+    }
+
+    public static Long getUserId() {
+        return getLoginUser().getUser().getId();
+    }
+}

+ 44 - 0
serve/src/main/java/space/anyi/BI/util/WebUtils.java

@@ -0,0 +1,44 @@
+package space.anyi.BI.util;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * @ProjectName: BI
+ * @FileName: WebUtils
+ * @Author: 杨逸
+ * @Data:2024/11/28 20:10
+ * @Description:
+ */
+public class WebUtils {
+    /**
+     * 将字符串渲染到客户端
+     *
+     * @param response 渲染对象
+     * @param string 待渲染的字符串
+     * @return null
+     */
+    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();
+        }
+    }
+
+
+    public static void setDownLoadHeader(String filename, HttpServletResponse response) throws UnsupportedEncodingException {
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        String fname= URLEncoder.encode(filename,"UTF-8").replaceAll("\\+", "%20");
+        response.setHeader("Content-disposition","attachment; filename="+fname);
+    }
+}

+ 29 - 0
serve/src/main/resources/application.yaml

@@ -0,0 +1,29 @@
+spring:
+  application:
+    name: BI
+  datasource:
+    driver-class-name: com.mysql.jdbc.Driver
+    url: jdbc:mysql://localhost:3306/bi?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
+    username: root
+    password: hsp
+  mvc:
+    pathmatch:
+      matching-strategy: ant_path_matcher
+  redis:
+    database: 12
+    host: anyi.space
+    port: 7000
+    password: yangyi
+server:
+  port: 8080
+  servlet:
+    context-path: /
+mybatis-plus:
+  global-config:
+    db-config:
+      logic-delete-field: deleteFlag
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  mapper-locations: classpath:/mapper/*.xml

+ 26 - 0
serve/src/main/resources/mapper/ChartMapper.xml

@@ -0,0 +1,26 @@
+<?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="space.anyi.BI.mapper.ChartMapper">
+
+    <resultMap id="BaseResultMap" type="space.anyi.BI.entity.Chart">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="analysisTarget" column="analysis_target" jdbcType="VARCHAR"/>
+            <result property="chartData" column="chart_data" jdbcType="VARCHAR"/>
+            <result property="chartType" column="chart_type" jdbcType="VARCHAR"/>
+            <result property="generatedChartData" column="generated_chart_data" jdbcType="VARCHAR"/>
+            <result property="analysisConclusion" column="analysis_conclusion" jdbcType="VARCHAR"/>
+            <result property="createdBy" column="created_by" jdbcType="BIGINT"/>
+            <result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
+            <result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
+            <result property="deleteFlag" column="delete_flag" jdbcType="TINYINT"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,analysis_target,chart_data,
+        chart_type,generated_chart_data,analysis_conclusion,
+        created_by,created_at,updated_at,
+        delete_flag
+    </sql>
+</mapper>

+ 24 - 0
serve/src/main/resources/mapper/UserMapper.xml

@@ -0,0 +1,24 @@
+<?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="space.anyi.BI.mapper.UserMapper">
+
+    <resultMap id="BaseResultMap" type="space.anyi.BI.entity.User">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="userAccount" column="user_account" jdbcType="VARCHAR"/>
+            <result property="userPassword" column="user_password" jdbcType="VARCHAR"/>
+            <result property="userName" column="user_name" jdbcType="VARCHAR"/>
+            <result property="userAvatar" column="user_avatar" jdbcType="VARCHAR"/>
+            <result property="userRole" column="user_role" jdbcType="VARCHAR"/>
+            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
+            <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
+            <result property="deleteFlag" column="delete_flag" jdbcType="TINYINT"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,user_account,user_password,
+        user_name,user_avatar,user_role,
+        create_time,update_time,delete_flag
+    </sql>
+</mapper>