From c17f4c38e9725686cae45323b342dd26d470c63e Mon Sep 17 00:00:00 2001
From: root <13910913995@163.com>
Date: Fri, 30 May 2025 09:58:52 +0800
Subject: [PATCH] Initial commit
---
.idea/.gitignore | 3 +
.idea/StdProject.iml | 9 +
.idea/compiler.xml | 19 +
.idea/encodings.xml | 6 +
.idea/jarRepositories.xml | 20 +
.idea/misc.xml | 14 +
.idea/modules.xml | 8 +
backend/db/db_mysql.sql | 242 +++
backend/db/db_sqllite.sql | 243 +++
backend/db/project.db | Bin 0 -> 35840 bytes
backend/pom.xml | 141 ++
.../com/stdproject/ProjectApplication.java | 32 +
.../stdproject/common/BusinessException.java | 106 +
.../java/com/stdproject/common/Constants.java | 121 ++
.../com/stdproject/common/DataPermission.java | 36 +
.../common/GlobalExceptionHandler.java | 188 ++
.../com/stdproject/common/OperationLog.java | 41 +
.../stdproject/common/OperationLogAspect.java | 172 ++
.../com/stdproject/common/PageRequest.java | 72 +
.../java/com/stdproject/common/Result.java | 191 ++
.../com/stdproject/common/ResultCode.java | 223 +++
.../com/stdproject/config/CacheConfig.java | 26 +
.../config/CustomUserDetailsService.java | 210 ++
.../config/JwtAuthenticationEntryPoint.java | 43 +
.../config/JwtAuthenticationFilter.java | 78 +
.../stdproject/config/MybatisPlusConfig.java | 40 +
.../config/PasswordEncoderConfig.java | 21 +
.../com/stdproject/config/SecurityConfig.java | 172 ++
.../com/stdproject/config/SwaggerConfig.java | 46 +
.../java/com/stdproject/config/WebConfig.java | 28 +
.../controller/AppDictionaryController.java | 270 +++
.../controller/AppMenuController.java | 378 ++++
.../controller/AppOptLogController.java | 248 +++
.../controller/AppOrganizationController.java | 221 +++
.../controller/AppRoleController.java | 320 +++
.../controller/AppUserController.java | 277 +++
.../stdproject/controller/AuthController.java | 258 +++
.../com/stdproject/entity/AppDictionary.java | 75 +
.../java/com/stdproject/entity/AppMenu.java | 110 ++
.../java/com/stdproject/entity/AppOptLog.java | 80 +
.../stdproject/entity/AppOrganization.java | 110 ++
.../java/com/stdproject/entity/AppRole.java | 85 +
.../com/stdproject/entity/AppRoleMenu.java | 44 +
.../com/stdproject/entity/AppRoleUser.java | 44 +
.../java/com/stdproject/entity/AppUser.java | 130 ++
.../mapper/AppDictionaryMapper.java | 18 +
.../com/stdproject/mapper/AppMenuMapper.java | 18 +
.../stdproject/mapper/AppOptLogMapper.java | 18 +
.../mapper/AppOrganizationMapper.java | 18 +
.../com/stdproject/mapper/AppRoleMapper.java | 18 +
.../stdproject/mapper/AppRoleMenuMapper.java | 18 +
.../stdproject/mapper/AppRoleUserMapper.java | 18 +
.../com/stdproject/mapper/AppUserMapper.java | 18 +
.../service/IAppDictionaryService.java | 16 +
.../stdproject/service/IAppMenuService.java | 16 +
.../stdproject/service/IAppOptLogService.java | 16 +
.../service/IAppOrganizationService.java | 16 +
.../service/IAppRoleMenuService.java | 16 +
.../stdproject/service/IAppRoleService.java | 16 +
.../service/IAppRoleUserService.java | 16 +
.../stdproject/service/IAppUserService.java | 43 +
.../impl/AppDictionaryServiceImpl.java | 20 +
.../service/impl/AppMenuServiceImpl.java | 20 +
.../service/impl/AppOptLogServiceImpl.java | 20 +
.../impl/AppOrganizationServiceImpl.java | 23 +
.../service/impl/AppRoleMenuServiceImpl.java | 20 +
.../service/impl/AppRoleServiceImpl.java | 20 +
.../service/impl/AppRoleUserServiceImpl.java | 20 +
.../service/impl/AppUserServiceImpl.java | 77 +
.../com/stdproject/utils/CaptchaUtils.java | 271 +++
.../java/com/stdproject/utils/FileUtils.java | 219 +++
.../java/com/stdproject/utils/JwtUtils.java | 168 ++
.../com/stdproject/utils/PasswordUtils.java | 85 +
backend/src/main/resources/.env | 14 +
backend/src/main/resources/application.yml | 193 ++
backend/src/main/resources/ehcache.xml | 58 +
backend/src/main/resources/logback-spring.xml | 64 +
backend/target/classes/.env | 14 +
backend/target/classes/application.yml | 193 ++
.../com/stdproject/ProjectApplication.class | Bin 0 -> 1443 bytes
.../stdproject/common/BusinessException.class | Bin 0 -> 2398 bytes
.../com/stdproject/common/Constants.class | Bin 0 -> 2248 bytes
.../stdproject/common/DataPermission.class | Bin 0 -> 628 bytes
.../common/GlobalExceptionHandler.class | Bin 0 -> 12414 bytes
.../com/stdproject/common/OperationLog.class | Bin 0 -> 651 bytes
.../common/OperationLogAspect.class | Bin 0 -> 6987 bytes
.../com/stdproject/common/PageRequest.class | Bin 0 -> 4925 bytes
.../com/stdproject/common/Result.class | Bin 0 -> 7245 bytes
.../com/stdproject/common/ResultCode.class | Bin 0 -> 5218 bytes
.../com/stdproject/config/CacheConfig.class | Bin 0 -> 743 bytes
.../config/CustomUserDetailsService.class | Bin 0 -> 9890 bytes
.../config/JwtAuthenticationEntryPoint.class | Bin 0 -> 2591 bytes
.../config/JwtAuthenticationFilter.class | Bin 0 -> 4263 bytes
.../stdproject/config/MybatisPlusConfig.class | Bin 0 -> 1683 bytes
.../config/PasswordEncoderConfig.class | Bin 0 -> 713 bytes
.../stdproject/config/SecurityConfig.class | Bin 0 -> 12482 bytes
.../com/stdproject/config/SwaggerConfig.class | Bin 0 -> 2932 bytes
.../com/stdproject/config/WebConfig.class | Bin 0 -> 1290 bytes
...ictionaryController$DictOrderRequest.class | Bin 0 -> 1043 bytes
.../controller/AppDictionaryController.class | Bin 0 -> 13885 bytes
.../AppMenuController$MenuOrderRequest.class | Bin 0 -> 1019 bytes
.../AppMenuController$MenuTreeNode.class | Bin 0 -> 3379 bytes
.../controller/AppMenuController.class | Bin 0 -> 14955 bytes
.../AppOptLogController$LogStatistics.class | Bin 0 -> 1385 bytes
.../controller/AppOptLogController.class | Bin 0 -> 12424 bytes
.../AppOrganizationController.class | Bin 0 -> 11715 bytes
.../controller/AppRoleController.class | Bin 0 -> 16425 bytes
...pUserController$PasswordResetRequest.class | Bin 0 -> 789 bytes
...UserController$PasswordUpdateRequest.class | Bin 0 -> 1002 bytes
.../controller/AppUserController.class | Bin 0 -> 12249 bytes
...AuthController$ChangePasswordRequest.class | Bin 0 -> 1224 bytes
.../AuthController$LoginRequest.class | Bin 0 -> 1704 bytes
.../controller/AuthController.class | Bin 0 -> 9489 bytes
.../com/stdproject/entity/AppDictionary.class | Bin 0 -> 6822 bytes
.../com/stdproject/entity/AppMenu.class | Bin 0 -> 10369 bytes
.../com/stdproject/entity/AppOptLog.class | Bin 0 -> 7214 bytes
.../stdproject/entity/AppOrganization.class | Bin 0 -> 10547 bytes
.../com/stdproject/entity/AppRole.class | Bin 0 -> 7764 bytes
.../com/stdproject/entity/AppRoleMenu.class | Bin 0 -> 3578 bytes
.../com/stdproject/entity/AppRoleUser.class | Bin 0 -> 3579 bytes
.../com/stdproject/entity/AppUser.class | Bin 0 -> 12817 bytes
.../mapper/AppDictionaryMapper.class | Bin 0 -> 406 bytes
.../com/stdproject/mapper/AppMenuMapper.class | Bin 0 -> 388 bytes
.../stdproject/mapper/AppOptLogMapper.class | Bin 0 -> 394 bytes
.../mapper/AppOrganizationMapper.class | Bin 0 -> 412 bytes
.../com/stdproject/mapper/AppRoleMapper.class | Bin 0 -> 388 bytes
.../stdproject/mapper/AppRoleMenuMapper.class | Bin 0 -> 400 bytes
.../stdproject/mapper/AppRoleUserMapper.class | Bin 0 -> 400 bytes
.../com/stdproject/mapper/AppUserMapper.class | Bin 0 -> 388 bytes
.../service/IAppDictionaryService.class | Bin 0 -> 338 bytes
.../stdproject/service/IAppMenuService.class | Bin 0 -> 320 bytes
.../service/IAppOptLogService.class | Bin 0 -> 326 bytes
.../service/IAppOrganizationService.class | Bin 0 -> 344 bytes
.../service/IAppRoleMenuService.class | Bin 0 -> 332 bytes
.../stdproject/service/IAppRoleService.class | Bin 0 -> 320 bytes
.../service/IAppRoleUserService.class | Bin 0 -> 332 bytes
.../stdproject/service/IAppUserService.class | Bin 0 -> 685 bytes
.../impl/AppDictionaryServiceImpl.class | Bin 0 -> 743 bytes
.../service/impl/AppMenuServiceImpl.class | Bin 0 -> 701 bytes
.../service/impl/AppOptLogServiceImpl.class | Bin 0 -> 715 bytes
.../impl/AppOrganizationServiceImpl.class | Bin 0 -> 757 bytes
.../service/impl/AppRoleMenuServiceImpl.class | Bin 0 -> 729 bytes
.../service/impl/AppRoleServiceImpl.class | Bin 0 -> 701 bytes
.../service/impl/AppRoleUserServiceImpl.class | Bin 0 -> 729 bytes
.../service/impl/AppUserServiceImpl.class | Bin 0 -> 3437 bytes
.../utils/CaptchaUtils$CaptchaResult.class | Bin 0 -> 804 bytes
.../com/stdproject/utils/CaptchaUtils.class | Bin 0 -> 6479 bytes
.../com/stdproject/utils/FileUtils.class | Bin 0 -> 6490 bytes
.../utils/JwtUtils$ClaimsResolver.class | Bin 0 -> 516 bytes
.../com/stdproject/utils/JwtUtils.class | Bin 0 -> 6153 bytes
.../com/stdproject/utils/PasswordUtils.class | Bin 0 -> 2438 bytes
backend/target/classes/ehcache.xml | 58 +
backend/target/classes/logback-spring.xml | 64 +
.../compile/default-compile/createdFiles.lst | 71 +
.../compile/default-compile/inputFiles.lst | 61 +
dev-prompt.md | 152 ++
logs/stdproject-backend/error.2025-05-29.log | 26 +
logs/stdproject-backend/error.log | 1729 +++++++++++++++++
logs/stdproject-backend/info.2025-05-29.log | 11 +
logs/stdproject-backend/info.log | 121 ++
projectframe.md | 28 +
161 files changed, 9320 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/StdProject.iml
create mode 100644 .idea/compiler.xml
create mode 100644 .idea/encodings.xml
create mode 100644 .idea/jarRepositories.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 backend/db/db_mysql.sql
create mode 100644 backend/db/db_sqllite.sql
create mode 100644 backend/db/project.db
create mode 100644 backend/pom.xml
create mode 100644 backend/src/main/java/com/stdproject/ProjectApplication.java
create mode 100644 backend/src/main/java/com/stdproject/common/BusinessException.java
create mode 100644 backend/src/main/java/com/stdproject/common/Constants.java
create mode 100644 backend/src/main/java/com/stdproject/common/DataPermission.java
create mode 100644 backend/src/main/java/com/stdproject/common/GlobalExceptionHandler.java
create mode 100644 backend/src/main/java/com/stdproject/common/OperationLog.java
create mode 100644 backend/src/main/java/com/stdproject/common/OperationLogAspect.java
create mode 100644 backend/src/main/java/com/stdproject/common/PageRequest.java
create mode 100644 backend/src/main/java/com/stdproject/common/Result.java
create mode 100644 backend/src/main/java/com/stdproject/common/ResultCode.java
create mode 100644 backend/src/main/java/com/stdproject/config/CacheConfig.java
create mode 100644 backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java
create mode 100644 backend/src/main/java/com/stdproject/config/JwtAuthenticationEntryPoint.java
create mode 100644 backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java
create mode 100644 backend/src/main/java/com/stdproject/config/MybatisPlusConfig.java
create mode 100644 backend/src/main/java/com/stdproject/config/PasswordEncoderConfig.java
create mode 100644 backend/src/main/java/com/stdproject/config/SecurityConfig.java
create mode 100644 backend/src/main/java/com/stdproject/config/SwaggerConfig.java
create mode 100644 backend/src/main/java/com/stdproject/config/WebConfig.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AppDictionaryController.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AppMenuController.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AppOptLogController.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AppOrganizationController.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AppRoleController.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AppUserController.java
create mode 100644 backend/src/main/java/com/stdproject/controller/AuthController.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppDictionary.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppMenu.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppOptLog.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppOrganization.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppRole.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppRoleMenu.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppRoleUser.java
create mode 100644 backend/src/main/java/com/stdproject/entity/AppUser.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppDictionaryMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppMenuMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppOptLogMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppOrganizationMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppRoleMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppRoleMenuMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppRoleUserMapper.java
create mode 100644 backend/src/main/java/com/stdproject/mapper/AppUserMapper.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppDictionaryService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppMenuService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppOptLogService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppOrganizationService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppRoleMenuService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppRoleService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppRoleUserService.java
create mode 100644 backend/src/main/java/com/stdproject/service/IAppUserService.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppDictionaryServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppMenuServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppOptLogServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppOrganizationServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppRoleMenuServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppRoleServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppRoleUserServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/service/impl/AppUserServiceImpl.java
create mode 100644 backend/src/main/java/com/stdproject/utils/CaptchaUtils.java
create mode 100644 backend/src/main/java/com/stdproject/utils/FileUtils.java
create mode 100644 backend/src/main/java/com/stdproject/utils/JwtUtils.java
create mode 100644 backend/src/main/java/com/stdproject/utils/PasswordUtils.java
create mode 100644 backend/src/main/resources/.env
create mode 100644 backend/src/main/resources/application.yml
create mode 100644 backend/src/main/resources/ehcache.xml
create mode 100644 backend/src/main/resources/logback-spring.xml
create mode 100644 backend/target/classes/.env
create mode 100644 backend/target/classes/application.yml
create mode 100644 backend/target/classes/com/stdproject/ProjectApplication.class
create mode 100644 backend/target/classes/com/stdproject/common/BusinessException.class
create mode 100644 backend/target/classes/com/stdproject/common/Constants.class
create mode 100644 backend/target/classes/com/stdproject/common/DataPermission.class
create mode 100644 backend/target/classes/com/stdproject/common/GlobalExceptionHandler.class
create mode 100644 backend/target/classes/com/stdproject/common/OperationLog.class
create mode 100644 backend/target/classes/com/stdproject/common/OperationLogAspect.class
create mode 100644 backend/target/classes/com/stdproject/common/PageRequest.class
create mode 100644 backend/target/classes/com/stdproject/common/Result.class
create mode 100644 backend/target/classes/com/stdproject/common/ResultCode.class
create mode 100644 backend/target/classes/com/stdproject/config/CacheConfig.class
create mode 100644 backend/target/classes/com/stdproject/config/CustomUserDetailsService.class
create mode 100644 backend/target/classes/com/stdproject/config/JwtAuthenticationEntryPoint.class
create mode 100644 backend/target/classes/com/stdproject/config/JwtAuthenticationFilter.class
create mode 100644 backend/target/classes/com/stdproject/config/MybatisPlusConfig.class
create mode 100644 backend/target/classes/com/stdproject/config/PasswordEncoderConfig.class
create mode 100644 backend/target/classes/com/stdproject/config/SecurityConfig.class
create mode 100644 backend/target/classes/com/stdproject/config/SwaggerConfig.class
create mode 100644 backend/target/classes/com/stdproject/config/WebConfig.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppDictionaryController$DictOrderRequest.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppDictionaryController.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppMenuController$MenuOrderRequest.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppMenuController$MenuTreeNode.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppMenuController.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppOptLogController$LogStatistics.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppOptLogController.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppOrganizationController.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppRoleController.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppUserController$PasswordResetRequest.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppUserController$PasswordUpdateRequest.class
create mode 100644 backend/target/classes/com/stdproject/controller/AppUserController.class
create mode 100644 backend/target/classes/com/stdproject/controller/AuthController$ChangePasswordRequest.class
create mode 100644 backend/target/classes/com/stdproject/controller/AuthController$LoginRequest.class
create mode 100644 backend/target/classes/com/stdproject/controller/AuthController.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppDictionary.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppMenu.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppOptLog.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppOrganization.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppRole.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppRoleMenu.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppRoleUser.class
create mode 100644 backend/target/classes/com/stdproject/entity/AppUser.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppDictionaryMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppMenuMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppOptLogMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppOrganizationMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppRoleMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppRoleMenuMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppRoleUserMapper.class
create mode 100644 backend/target/classes/com/stdproject/mapper/AppUserMapper.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppDictionaryService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppMenuService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppOptLogService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppOrganizationService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppRoleMenuService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppRoleService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppRoleUserService.class
create mode 100644 backend/target/classes/com/stdproject/service/IAppUserService.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppDictionaryServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppMenuServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppOptLogServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppOrganizationServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppRoleMenuServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppRoleServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppRoleUserServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/service/impl/AppUserServiceImpl.class
create mode 100644 backend/target/classes/com/stdproject/utils/CaptchaUtils$CaptchaResult.class
create mode 100644 backend/target/classes/com/stdproject/utils/CaptchaUtils.class
create mode 100644 backend/target/classes/com/stdproject/utils/FileUtils.class
create mode 100644 backend/target/classes/com/stdproject/utils/JwtUtils$ClaimsResolver.class
create mode 100644 backend/target/classes/com/stdproject/utils/JwtUtils.class
create mode 100644 backend/target/classes/com/stdproject/utils/PasswordUtils.class
create mode 100644 backend/target/classes/ehcache.xml
create mode 100644 backend/target/classes/logback-spring.xml
create mode 100644 backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
create mode 100644 backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
create mode 100644 dev-prompt.md
create mode 100644 logs/stdproject-backend/error.2025-05-29.log
create mode 100644 logs/stdproject-backend/error.log
create mode 100644 logs/stdproject-backend/info.2025-05-29.log
create mode 100644 logs/stdproject-backend/info.log
create mode 100644 projectframe.md
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/.idea/StdProject.iml b/.idea/StdProject.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/StdProject.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b56de12
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..dfc8d74
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..5a2f139
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..ac9352c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..fbb69cc
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/db/db_mysql.sql b/backend/db/db_mysql.sql
new file mode 100644
index 0000000..52e6b58
--- /dev/null
+++ b/backend/db/db_mysql.sql
@@ -0,0 +1,242 @@
+/*
+ Navicat Premium Dump SQL
+
+ Source Server : 华为云-mysql数据库
+ Source Server Type : MySQL
+ Source Server Version : 80403 (8.4.3)
+ Source Host : 121.37.111.42:3306
+ Source Schema : gisbi-demodb
+
+ Target Server Type : MySQL
+ Target Server Version : 80403 (8.4.3)
+ File Encoding : 65001
+
+ Date: 30/05/2025 08:58:25
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for app_application
+-- ----------------------------
+DROP TABLE IF EXISTS `app_application`;
+CREATE TABLE `app_application` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
+ `type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '01' COMMENT '应用类型 01-数据展示分析 ',
+ `code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用编号 自动生成:000001',
+ `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用名称',
+ `simplename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用简称',
+ `createdate` datetime NULL DEFAULT NULL COMMENT '创建日期',
+ `description` varchar(600) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用描述',
+ `image` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '应用图片 base64存储',
+ `appconfig` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '应用配置 JSON格式存储其他配置相关信息',
+ `dbconfig` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '数据库配置 JSON格式存储数据库信息配置(数据库用于存储此应用系统的所有配置数据)',
+ `organization` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户单位 使用这个应用系统的组织单位',
+ `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态 01-初始创建 02-正常运行 09-已停用',
+ `lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
+ `lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
+ `custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_dictionary
+-- ----------------------------
+DROP TABLE IF EXISTS `app_dictionary`;
+CREATE TABLE `app_dictionary` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `orderno` int NOT NULL COMMENT '顺序号',
+ `dictcode` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典编码',
+ `dictname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名称',
+ `dictdata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '字典数据 JSON数组[{\"code\":\"01\",\"name\":\"字典值\"}]',
+ `lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
+ `custom1` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用-数据字典' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `app_menu`;
+CREATE TABLE `app_menu` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单类型 01-菜单',
+ `code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单编号',
+ `orderno` int NULL DEFAULT NULL COMMENT '同级序号',
+ `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
+ `icon` varchar(9000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单图标 base64存储',
+ `islink` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否外链 0-非外部 1-是外链',
+ `url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '菜单URL 内部资源页面URL访问地址',
+ `module_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模块ID 菜单关联的模块ID',
+ `parentid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级菜单ID 顶级为0',
+ `isdisplay` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否显示 0-不显示 1-显示',
+ `lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
+ `lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
+ `custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_菜单' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_module
+-- ----------------------------
+DROP TABLE IF EXISTS `app_module`;
+CREATE TABLE `app_module` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称',
+ `pid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级ID 父级模块',
+ `level` int NULL DEFAULT NULL COMMENT '层级',
+ `node_type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '节点类型 01-目录 02-模块',
+ `type` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型 01-登录页面 02-主框架页面 03-GIS大屏页面 04-二级弹窗页面 05-数据填报页面 06-文档资源页面09-自定义页面',
+ `canvas_style_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '样式数据',
+ `component_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '组件数据',
+ `status` int NULL DEFAULT 1 COMMENT '状态 0-未发布 1-已发布',
+ `self_watermark_status` int NULL DEFAULT 0 COMMENT '是否单独打开水印 0-关闭 1-开启',
+ `sort` int NULL DEFAULT 0 COMMENT '排序 目录内的排序号',
+ `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
+ `create_by` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
+ `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
+ `update_by` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
+ `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据来源',
+ `delete_flag` tinyint NULL DEFAULT 0 COMMENT '删除标志',
+ `delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
+ `delete_by` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '删除人',
+ `version` int NULL DEFAULT 0 COMMENT '可视化资源版本',
+ `content_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '内容标识',
+ `check_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '内容检查标识',
+ `custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用_系统模块' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_optlog
+-- ----------------------------
+DROP TABLE IF EXISTS `app_optlog`;
+CREATE TABLE `app_optlog` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
+ `usercode` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户账号',
+ `username` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名称',
+ `opttype` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '操作类型 00-登录 01-新增 02-修改 03-删除 06-查询 09其他',
+ `module` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '模块名称',
+ `description` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '日志描述',
+ `method` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '操作方法',
+ `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '方法参数',
+ `logtime` datetime NULL DEFAULT NULL COMMENT '创建时间',
+ `requestip` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '请求IP',
+ `browser` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '浏览器类型',
+ PRIMARY KEY (`id`) USING BTREE,
+ INDEX `log_create_time_index`(`logtime` ASC) USING BTREE,
+ INDEX `inx_log_type`(`opttype` ASC) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '系统操作日志' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for app_organization
+-- ----------------------------
+DROP TABLE IF EXISTS `app_organization`;
+CREATE TABLE `app_organization` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `orgtype` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织类型:01-公司 02-部门',
+ `orgcode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织编号',
+ `orgname` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织名称',
+ `parentid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上级ID',
+ `manager` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织负责人',
+ `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织详情',
+ `address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系地址',
+ `contact_phone` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话',
+ `contact_person` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系人',
+ `isvaild` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '是否有效 1-是 0-否',
+ `lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
+ `lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
+ `custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_用户组织' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_role
+-- ----------------------------
+DROP TABLE IF EXISTS `app_role`;
+CREATE TABLE `app_role` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `rolecode` varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色编号 系统生成,三位编号',
+ `rolename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
+ `type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色类别 1-应用管理员 2-应用普通用户',
+ `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色描述',
+ `isvaild` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '是否有效 1-是 0-否',
+ `lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '最近修改者',
+ `lastmodifydate` datetime NOT NULL COMMENT '最近修改日期',
+ `custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_系统角色' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `app_role_menu`;
+CREATE TABLE `app_role_menu` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `roleid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色id',
+ `menuid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色-数据权限对照' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_role_users
+-- ----------------------------
+DROP TABLE IF EXISTS `app_role_users`;
+CREATE TABLE `app_role_users` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'id',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `roleid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色id',
+ `userid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户id',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色-用户对照' ROW_FORMAT = DYNAMIC;
+
+-- ----------------------------
+-- Table structure for app_user
+-- ----------------------------
+DROP TABLE IF EXISTS `app_user`;
+CREATE TABLE `app_user` (
+ `id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID',
+ `app_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '应用ID 关联应用系统',
+ `orgid` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属组织',
+ `usertype` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户类型 0-管理员 1-普通用户',
+ `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称 用户名称(账号)',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称(中文)',
+ `password` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录密码',
+ `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
+ `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
+ `avatar` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '用户头像 base64存储用户头像',
+ `status` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '状态 1-有效 0-停用',
+ `pwdvalidperiod` int NULL DEFAULT 90 COMMENT '密码有限期 密码有限期(天)',
+ `failednum` int NULL DEFAULT 0 COMMENT '登录失败次数 允许的登录失败次数',
+ `loginip` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户指定登录IP 如果设置了IP,则只允许IP用户登录',
+ `failedlocktime` datetime NULL DEFAULT NULL COMMENT '登录失败锁定时间',
+ `pwdresettime` datetime NULL DEFAULT NULL COMMENT '密码修改时间',
+ `lastmodifier` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近修改者',
+ `lastmodifydate` datetime NULL DEFAULT NULL COMMENT '最近修改日期',
+ `custom1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用1',
+ `custom2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用2',
+ `custom3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备用3',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '应用系统_系统用户' ROW_FORMAT = DYNAMIC;
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/backend/db/db_sqllite.sql b/backend/db/db_sqllite.sql
new file mode 100644
index 0000000..53c751c
--- /dev/null
+++ b/backend/db/db_sqllite.sql
@@ -0,0 +1,243 @@
+/*
+Navicat SQLite Data Transfer
+
+Source Server : project
+Source Server Version : 30808
+Source Host : :0
+
+Target Server Type : SQLite
+Target Server Version : 30808
+File Encoding : 65001
+
+Date: 2025-05-30 09:13:56
+*/
+
+PRAGMA foreign_keys = OFF;
+
+-- ----------------------------
+-- Table structure for app_application
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_application";
+CREATE TABLE "app_application" (
+"id" TEXT(40) NOT NULL,
+"type" TEXT(2),
+"code" TEXT(6),
+"name" TEXT(200) NOT NULL,
+"simplename" TEXT(100),
+"createdate" TEXT,
+"description" TEXT(600),
+"image" TEXT,
+"appconfig" TEXT,
+"dbconfig" TEXT,
+"organization" TEXT(200),
+"status" TEXT(20),
+"lastmodifier" TEXT(40),
+"lastmodifydate" TEXT,
+"custom1" TEXT(100),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_dictionary
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_dictionary";
+CREATE TABLE "app_dictionary" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"orderno" INTEGER NOT NULL,
+"dictcode" TEXT(40) NOT NULL,
+"dictname" TEXT(100) NOT NULL,
+"dictdata" TEXT,
+"lastmodifydate" TEXT,
+"custom1" TEXT(1000),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_menu
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_menu";
+CREATE TABLE "app_menu" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"type" TEXT(2) NOT NULL,
+"code" TEXT(20) NOT NULL,
+"orderno" INTEGER,
+"name" TEXT(100) NOT NULL,
+"icon" TEXT(9000),
+"islink" TEXT(2),
+"url" TEXT(200),
+"module_id" TEXT(40),
+"parentid" TEXT(40),
+"isdisplay" TEXT(2),
+"lastmodifier" TEXT(40),
+"lastmodifydate" TEXT,
+"custom1" TEXT(100),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_module
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_module";
+CREATE TABLE "app_module" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"name" TEXT(200),
+"pid" TEXT(40),
+"level" INTEGER,
+"node_type" TEXT(40),
+"type" TEXT(40),
+"canvas_style_data" TEXT,
+"component_data" TEXT,
+"status" INTEGER,
+"self_watermark_status" INTEGER,
+"sort" INTEGER,
+"create_time" TEXT,
+"create_by" TEXT(40),
+"update_time" TEXT,
+"update_by" TEXT(40),
+"remark" TEXT(255),
+"source" TEXT(255),
+"delete_flag" INTEGER,
+"delete_time" TEXT,
+"delete_by" TEXT(40),
+"version" INTEGER,
+"content_id" TEXT(50),
+"check_version" TEXT(50),
+"custom1" TEXT(100),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_optlog
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_optlog";
+CREATE TABLE "app_optlog" (
+"id" TEXT(40) NOT NULL,
+"usercode" TEXT(200),
+"username" TEXT(200),
+"opttype" TEXT(20),
+"module" TEXT(200),
+"description" TEXT(400),
+"method" TEXT(200),
+"params" TEXT,
+"logtime" TEXT,
+"requestip" TEXT(200),
+"browser" TEXT(200),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_organization
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_organization";
+CREATE TABLE "app_organization" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"orgtype" TEXT(20),
+"orgcode" TEXT(20),
+"orgname" TEXT(200),
+"parentid" TEXT(40),
+"manager" TEXT(40),
+"description" TEXT(1000),
+"address" TEXT(100),
+"contact_phone" TEXT(100),
+"contact_person" TEXT(100),
+"isvaild" TEXT(2) NOT NULL,
+"lastmodifier" TEXT(40),
+"lastmodifydate" TEXT,
+"custom1" TEXT(100),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_role
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_role";
+CREATE TABLE "app_role" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"rolecode" TEXT(3) NOT NULL,
+"rolename" TEXT(100) NOT NULL,
+"type" TEXT(2) NOT NULL,
+"description" TEXT(1000),
+"isvaild" TEXT(2) NOT NULL,
+"lastmodifier" TEXT(40) NOT NULL,
+"lastmodifydate" TEXT NOT NULL,
+"custom1" TEXT(100),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_role_menu";
+CREATE TABLE "app_role_menu" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"roleid" TEXT(40) NOT NULL,
+"menuid" TEXT(40) NOT NULL,
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_role_users
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_role_users";
+CREATE TABLE "app_role_users" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"roleid" TEXT(40) NOT NULL,
+"userid" TEXT(40) NOT NULL,
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Table structure for app_user
+-- ----------------------------
+DROP TABLE IF EXISTS "main"."app_user";
+CREATE TABLE "app_user" (
+"id" TEXT(40) NOT NULL,
+"app_id" TEXT(40),
+"orgid" TEXT(40) NOT NULL,
+"usertype" TEXT(20) NOT NULL,
+"username" TEXT(100) NOT NULL,
+"nickname" TEXT(255) NOT NULL,
+"password" TEXT(200),
+"email" TEXT(100),
+"phone" TEXT(20),
+"avatar" TEXT,
+"status" TEXT(2),
+"pwdvalidperiod" INTEGER,
+"failednum" INTEGER,
+"loginip" TEXT(40),
+"failedlocktime" TEXT,
+"pwdresettime" TEXT,
+"lastmodifier" TEXT(40),
+"lastmodifydate" TEXT,
+"custom1" TEXT(100),
+"custom2" TEXT(100),
+"custom3" TEXT(100),
+PRIMARY KEY ("id")
+);
+
+-- ----------------------------
+-- Indexes structure for table app_optlog
+-- ----------------------------
+CREATE INDEX "main"."inx_log_type"
+ON "app_optlog" ("opttype" ASC);
+CREATE INDEX "main"."log_create_time_index"
+ON "app_optlog" ("logtime" ASC);
diff --git a/backend/db/project.db b/backend/db/project.db
new file mode 100644
index 0000000000000000000000000000000000000000..6fdd66e2d56167770e21388d200fd6cd2b02d45f
GIT binary patch
literal 35840
zcmeHQTWlOx8J?MA@5XDpsp}+5N|TPaisB{7dhWZkQ3YvmjA<@yVw46{wv*j)GRoe%
zvrg(>9ukN2h6qJ!K|-oj1cDbJpdwT%gv1*U2+9j@yg3OHFBKA^0wMl0*E750OI#G2=n
z$`bbr-R@>`r=IJD&|Y*4$N4P!5BeAS82!V2Tqqf(Qv@gitBb(4A;@nleu)RRz^N|*
zrvw^=bL6eY&-9=OPy{vw0x8@|a#Y_Gt)`8n2v7tt0(AVR{eU8{c@d!F|K{aAZ8=3?
z5dD`yOp{^%f_}?1*|*{Gt{zuPD4EV?*{ic{<80M5n$1$hDz~jh-Du55pL!0T96vNQ
z&QBeB;^;V^i3Vo)(ZN*4s$_V6YW$h0QANn|$4^Z0$G>v)=za*nu_K|F*l1PER=ojf
zCXY{zPmG_8B*FD4H!9{rS>o`zQ47LFp`%P3p<=WRF9Td+)v(*OM#Y+*g#bS#4k*vq
z?M6-X%YwXwY)F>~Izg5@$WNV|Ja*{h8UD%fGyEt9BRiPN&YfqI>3#dySCaw407lg+
z8xG2V3-QQxV@!lD1#)|~>9=WAa_dRpr#Tc7F{u+V+p0CICK&~T>fq2a0k96xDyCg-
zSxwvy9|_HiZ`F)*eqC^SXhpeEpSI3<5izZDwu{tgoipmziw-n?mbjKUy4^O~Gj>RI
zhyg<_v*iat-^1~Pprv{3Hqq$nCS~-fqT}5D!dluyXNrp3+Np3&0&G}hLiF42O
zCeyoiv9AsXVrR{)&tRDYUz6Q}CMGW>VdCUnVtiEWjL7h${4RBf=Uvi27TP*x&q!_-
z$cw&^r~4ARWmm2G1)uDSy)&(9K=7nTkosq;W_RVAM$4?XyV_*g70Yf`joBcds7$#Y
z#LclpB7Jz*V&W!dsVisVyDi+iOwYldMEXQ_u|(ng&rKRW!@?2v>`FQ#O|x7CsQkZN
zq-Z>f!2O2+mH+QQp3)Xk1eS{c9{)Ku!LSG6^hfyFjE637Nu(!6786oEao*9Py7&Fx
zcp{lhkBqQyoCs$;j?nMSd7Qgc%&f-=Skb(PoU8as>SotWqH10=@odC1aX6D4(8`y{
zS(B%+jvU8N6=h?%KV~$ou~wsMe(fb}yeigbs@472ax)1J8JKU2
znf)0Y?#$|o8Slw?4T>2z)A_f)Cjs5*$&d!8Agx5j%*|iDdHd&Y-@JZZy8XKw5Ng>M
z4XYZI78q^t1ly}YKywW_9@V$l6~Qpnvg+p`FP9`iWjjc6NP6X2Ns!b7f_gv}rUbnx
z6^eqG6Gd5(WNh^-l0V|H#*0?9YK*Blfge2$cAUl~n?F9qi$YE~$OA!B4)QN(N|rwa
zHa+vSdG<-GJ*LX}oUHMqPkw3Y*wOub)w*Ev6VRC&S^n_(R-4(!uADs9koF2t*{%({)A2BKJ69#>N-b1(1>*#rO1|33KGz3I?Pz3H*1ky-m-62bZ
zSG*uvkJrU9+v7pE*d^lyOYY#yuhg@vbPsi|#hz<=B
z3!*NHs;cy2`M-zBG29LAC8hy4pNmNxkUl?Z>aYlc
z1;LF-o?D<6-99`rw0CRzP*9>IglbVpg^QFzMYJGBw9RqFN}m2xk_#GHntR^_UF7!R
zM~C*1Wr7YC^o5d=m*LkfQw+)^iUj?3Y>A+9RCBVZ>6!-zSuP&`5&D2Ze@7p|g&q_E
zioixdAXMmqEg}30qL3c>R&hI!+~Es
z+wV$fBFFZ;64t2LSYs1DrMtB@Vc=4l8=O^WPxZ{hIC+E^S=)=NuwZDcsziSnElchkjKuJb&!GZ&mF)ji|F>G-I+_nfVB;Y`^?w_WXSBu?fz?8Qj{mELB+Z8+
zu<;O}6xj=TPB-MH{}WcJV6+Ui2ZsZ69o}==)tHs?g1tHNL19pZ>N-nR%#&8)*>4}X&?j6T_{YDn$Pj?owSd@gEB+0P<81@DA
zrqDl`6ndY5|HwCwK7lX5U(uh@AJFg7hd`tUMSvo(?hr_zAyyCv+)KZENx7Fk_p-&k
zB;8BGz4W*j{MCT}{pUXP4ssm)BTt=yU;MNDgZlrtPyBy?kInxrpY1f}{e=M5|8q>8
z;qq|0>&M)Y-dLwWq)US3I-A){PJ=q6wtMC_WmGb(@Ii
zB-(9ax=mC?wf5a+?#Px{Mv|_sC?jJTNoo?;o{`S`&lW)bhZ8-PjlkuX`(xZjYNX4!
z<@Wz&c+JF{)d@BC+IO3|BdNIeKjIp1x$WOn6S=50V(l4;-+z`XGVn_eiohKpFn4$$
z_yimd9S29eX@1I2K-B-U>mEAT#p}Cv_I!O1PN1
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.0
+
+
+ com.stdproject
+ stdproject-backend
+ 0.0.1-SNAPSHOT
+ stdproject-backend
+ Standard Project Backend
+
+ 17
+ 3.5.3
+ 0.11.5
+ 2.0.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ javax.cache
+ cache-api
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatis-plus.version}
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.30
+ runtime
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.36.0.3
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt.version}
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt.version}
+ runtime
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${springdoc.version}
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ com.baomidou
+ mybatis-plus-core
+ 3.5.3.1
+
+
+ org.projectlombok
+ lombok
+ 1.18.34 provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/ProjectApplication.java b/backend/src/main/java/com/stdproject/ProjectApplication.java
new file mode 100644
index 0000000..1ecc101
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/ProjectApplication.java
@@ -0,0 +1,32 @@
+package com.stdproject;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * Spring Boot 应用启动类
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@SpringBootApplication
+@MapperScan("com.stdproject.mapper")
+@EnableCaching
+@EnableAsync
+@EnableTransactionManagement
+public class ProjectApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ProjectApplication.class, args);
+ System.out.println("\n========================================");
+ System.out.println(" StdProject 应用启动成功!");
+ System.out.println(" 接口文档地址: http://localhost:8080/swagger-ui.html");
+ System.out.println(" 应用端口: 8080");
+ System.out.println("========================================\n");
+ }
+
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/BusinessException.java b/backend/src/main/java/com/stdproject/common/BusinessException.java
new file mode 100644
index 0000000..216b193
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/BusinessException.java
@@ -0,0 +1,106 @@
+package com.stdproject.common;
+
+/**
+ * 业务异常类
+ *
+ * @author StdProject
+ */
+public class BusinessException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误消息
+ */
+ private String message;
+
+ public BusinessException() {
+ super();
+ }
+
+ public BusinessException(String message) {
+ super(message);
+ this.message = message;
+ this.code = ResultCode.ERROR.getCode();
+ }
+
+ public BusinessException(Integer code, String message) {
+ super(message);
+ this.code = code;
+ this.message = message;
+ }
+
+ public BusinessException(ResultCode resultCode) {
+ super(resultCode.getMessage());
+ this.code = resultCode.getCode();
+ this.message = resultCode.getMessage();
+ }
+
+ public BusinessException(String message, Throwable cause) {
+ super(message, cause);
+ this.message = message;
+ this.code = ResultCode.ERROR.getCode();
+ }
+
+ public BusinessException(Integer code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
+ this.message = message;
+ }
+
+ public BusinessException(ResultCode resultCode, Throwable cause) {
+ super(resultCode.getMessage(), cause);
+ this.code = resultCode.getCode();
+ this.message = resultCode.getMessage();
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public void setCode(Integer code) {
+ this.code = code;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * 抛出业务异常
+ *
+ * @param message 错误消息
+ */
+ public static void throwException(String message) {
+ throw new BusinessException(message);
+ }
+
+ /**
+ * 抛出业务异常
+ *
+ * @param code 错误码
+ * @param message 错误消息
+ */
+ public static void throwException(Integer code, String message) {
+ throw new BusinessException(code, message);
+ }
+
+ /**
+ * 抛出业务异常
+ *
+ * @param resultCode 结果码枚举
+ */
+ public static void throwException(ResultCode resultCode) {
+ throw new BusinessException(resultCode);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/Constants.java b/backend/src/main/java/com/stdproject/common/Constants.java
new file mode 100644
index 0000000..63b4221
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/Constants.java
@@ -0,0 +1,121 @@
+package com.stdproject.common;
+
+/**
+ * 系统常量类
+ *
+ * @author StdProject
+ */
+public class Constants {
+
+ /**
+ * JWT相关常量
+ */
+ public static final String JWT_HEADER = "Authorization";
+ public static final String JWT_PREFIX = "Bearer ";
+ public static final String JWT_SECRET_KEY = "jwt.secret";
+ public static final String JWT_EXPIRATION_KEY = "jwt.expiration";
+
+ /**
+ * 用户状态
+ */
+ public static final String USER_STATUS_NORMAL = "0"; // 正常
+ public static final String USER_STATUS_DISABLED = "1"; // 停用
+ public static final String USER_STATUS_LOCKED = "2"; // 锁定
+
+ /**
+ * 角色状态
+ */
+ public static final String ROLE_STATUS_NORMAL = "0"; // 正常
+ public static final String ROLE_STATUS_DISABLED = "1"; // 停用
+
+ /**
+ * 菜单类型
+ */
+ public static final String MENU_TYPE_DIRECTORY = "M"; // 目录
+ public static final String MENU_TYPE_MENU = "C"; // 菜单
+ public static final String MENU_TYPE_BUTTON = "F"; // 按钮
+
+ /**
+ * 菜单状态
+ */
+ public static final String MENU_STATUS_NORMAL = "0"; // 正常
+ public static final String MENU_STATUS_DISABLED = "1"; // 停用
+
+ /**
+ * 组织状态
+ */
+ public static final String ORG_STATUS_NORMAL = "0"; // 正常
+ public static final String ORG_STATUS_DISABLED = "1"; // 停用
+
+ /**
+ * 操作类型
+ */
+ public static final String OPT_TYPE_LOGIN = "00"; // 登录
+ public static final String OPT_TYPE_INSERT = "01"; // 新增
+ public static final String OPT_TYPE_UPDATE = "02"; // 修改
+ public static final String OPT_TYPE_DELETE = "03"; // 删除
+ public static final String OPT_TYPE_QUERY = "06"; // 查询
+ public static final String OPT_TYPE_OTHER = "09"; // 其他
+
+ /**
+ * 操作状态
+ */
+ public static final String OPT_STATUS_SUCCESS = "0"; // 成功
+ public static final String OPT_STATUS_FAIL = "1"; // 失败
+
+ /**
+ * 数据权限类型
+ */
+ public static final String DATA_SCOPE_ALL = "1"; // 全部数据权限
+ public static final String DATA_SCOPE_CUSTOM = "2"; // 自定义数据权限
+ public static final String DATA_SCOPE_DEPT = "3"; // 部门数据权限
+ public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; // 部门及以下数据权限
+ public static final String DATA_SCOPE_SELF = "5"; // 仅本人数据权限
+
+ /**
+ * 字典状态
+ */
+ public static final String DICT_STATUS_NORMAL = "0"; // 正常
+ public static final String DICT_STATUS_DISABLED = "1"; // 停用
+
+ /**
+ * 是否删除
+ */
+ public static final String DELETED_YES = "1"; // 已删除
+ public static final String DELETED_NO = "0"; // 未删除
+
+ /**
+ * 性别
+ */
+ public static final String GENDER_MALE = "0"; // 男
+ public static final String GENDER_FEMALE = "1"; // 女
+ public static final String GENDER_UNKNOWN = "2"; // 未知
+
+ /**
+ * 默认密码
+ */
+ public static final String DEFAULT_PASSWORD = "123456";
+
+ /**
+ * 超级管理员角色编码
+ */
+ public static final String SUPER_ADMIN_ROLE = "admin";
+
+ /**
+ * 根节点ID
+ */
+ public static final Long ROOT_NODE_ID = 0L;
+
+ /**
+ * 缓存过期时间(秒)
+ */
+ public static final int CACHE_EXPIRE_TIME = 3600; // 1小时
+ public static final int CACHE_EXPIRE_TIME_SHORT = 300; // 5分钟
+ public static final int CACHE_EXPIRE_TIME_LONG = 86400; // 24小时
+
+ /**
+ * 分页默认值
+ */
+ public static final int DEFAULT_PAGE_SIZE = 20;
+ public static final int MAX_PAGE_SIZE = 500;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/DataPermission.java b/backend/src/main/java/com/stdproject/common/DataPermission.java
new file mode 100644
index 0000000..7278902
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/DataPermission.java
@@ -0,0 +1,36 @@
+package com.stdproject.common;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限注解
+ * 用于标记需要进行数据权限控制的方法
+ *
+ * @author StdProject
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+ /**
+ * 数据权限类型
+ * 1-全部数据权限 2-自定义数据权限 3-部门数据权限 4-部门及以下数据权限 5-仅本人数据权限
+ */
+ String value() default "1";
+
+ /**
+ * 表别名
+ */
+ String tableAlias() default "";
+
+ /**
+ * 用户ID字段名
+ */
+ String userIdColumn() default "create_user";
+
+ /**
+ * 部门ID字段名
+ */
+ String deptIdColumn() default "dept_id";
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/GlobalExceptionHandler.java b/backend/src/main/java/com/stdproject/common/GlobalExceptionHandler.java
new file mode 100644
index 0000000..e031221
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/GlobalExceptionHandler.java
@@ -0,0 +1,188 @@
+package com.stdproject.common;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+
+import java.sql.SQLException;
+import java.util.Set;
+
+/**
+ * 全局异常处理器
+ *
+ * @author StdProject
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ /**
+ * 处理业务异常
+ */
+ @ExceptionHandler(BusinessException.class)
+ @ResponseStatus(HttpStatus.OK)
+ public Result handleBusinessException(BusinessException e, HttpServletRequest request) {
+ log.warn("业务异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.error(e.getCode(), e.getMessage());
+ }
+
+ /**
+ * 处理参数校验异常
+ */
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
+ log.warn("参数校验异常: {} - {}", request.getRequestURI(), e.getMessage());
+ StringBuilder message = new StringBuilder();
+ for (FieldError error : e.getBindingResult().getFieldErrors()) {
+ message.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ");
+ }
+ return Result.badRequest(message.toString());
+ }
+
+ /**
+ * 处理参数绑定异常
+ */
+ @ExceptionHandler(BindException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Result handleBindException(BindException e, HttpServletRequest request) {
+ log.warn("参数绑定异常: {} - {}", request.getRequestURI(), e.getMessage());
+ StringBuilder message = new StringBuilder();
+ for (FieldError error : e.getBindingResult().getFieldErrors()) {
+ message.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ");
+ }
+ return Result.badRequest(message.toString());
+ }
+
+ /**
+ * 处理约束违反异常
+ */
+ @ExceptionHandler(ConstraintViolationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Result handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
+ log.warn("约束违反异常: {} - {}", request.getRequestURI(), e.getMessage());
+ StringBuilder message = new StringBuilder();
+ Set> violations = e.getConstraintViolations();
+ for (ConstraintViolation> violation : violations) {
+ message.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("; ");
+ }
+ return Result.badRequest(message.toString());
+ }
+
+ /**
+ * 处理参数类型不匹配异常
+ */
+ @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Result handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
+ log.warn("参数类型不匹配异常: {} - {}", request.getRequestURI(), e.getMessage());
+ String message = String.format("参数 '%s' 的值 '%s' 类型不正确", e.getName(), e.getValue());
+ return Result.badRequest(message);
+ }
+
+ /**
+ * 处理认证异常
+ */
+ @ExceptionHandler(AuthenticationException.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public Result handleAuthenticationException(AuthenticationException e, HttpServletRequest request) {
+ log.warn("认证异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.unauthorized();
+ }
+
+ /**
+ * 处理凭证错误异常
+ */
+ @ExceptionHandler(BadCredentialsException.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public Result handleBadCredentialsException(BadCredentialsException e, HttpServletRequest request) {
+ log.warn("凭证错误异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.error(ResultCode.LOGIN_ERROR);
+ }
+
+ /**
+ * 处理访问拒绝异常
+ */
+ @ExceptionHandler(AccessDeniedException.class)
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ public Result handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
+ log.warn("访问拒绝异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.forbidden();
+ }
+
+ /**
+ * 处理请求方法不支持异常
+ */
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
+ public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
+ log.warn("请求方法不支持异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.error(ResultCode.METHOD_NOT_ALLOWED);
+ }
+
+ /**
+ * 处理资源未找到异常
+ */
+ @ExceptionHandler(NoHandlerFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public Result handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
+ log.warn("资源未找到异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.notFound();
+ }
+
+ /**
+ * 处理数据完整性违反异常
+ */
+ @ExceptionHandler(DataIntegrityViolationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Result handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) {
+ log.warn("数据完整性违反异常: {} - {}", request.getRequestURI(), e.getMessage());
+ return Result.error(ResultCode.DATA_INTEGRITY_VIOLATION);
+ }
+
+ /**
+ * 处理SQL异常
+ */
+ @ExceptionHandler(SQLException.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public Result handleSQLException(SQLException e, HttpServletRequest request) {
+ log.error("SQL异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
+ return Result.error(ResultCode.DATABASE_ERROR);
+ }
+
+ /**
+ * 处理运行时异常
+ */
+ @ExceptionHandler(RuntimeException.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public Result handleRuntimeException(RuntimeException e, HttpServletRequest request) {
+ log.error("运行时异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
+ return Result.error(ResultCode.ERROR.getCode(), e.getMessage());
+ }
+
+ /**
+ * 处理其他异常
+ */
+ @ExceptionHandler(Exception.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public Result handleException(Exception e, HttpServletRequest request) {
+ log.error("系统异常: {} - {}", request.getRequestURI(), e.getMessage(), e);
+ return Result.error(ResultCode.UNKNOWN_ERROR);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/OperationLog.java b/backend/src/main/java/com/stdproject/common/OperationLog.java
new file mode 100644
index 0000000..150d4bf
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/OperationLog.java
@@ -0,0 +1,41 @@
+package com.stdproject.common;
+
+import java.lang.annotation.*;
+
+/**
+ * 操作日志注解
+ * 用于标记需要记录操作日志的方法
+ *
+ * @author StdProject
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface OperationLog {
+
+ /**
+ * 操作类型
+ * 00-登录 01-新增 02-修改 03-删除 06-查询 09-其他
+ */
+ String type() default "09";
+
+ /**
+ * 模块名称
+ */
+ String module() default "";
+
+ /**
+ * 操作描述
+ */
+ String description() default "";
+
+ /**
+ * 是否记录请求参数
+ */
+ boolean recordParams() default true;
+
+ /**
+ * 是否记录返回结果
+ */
+ boolean recordResult() default false;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java
new file mode 100644
index 0000000..ee8485e
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java
@@ -0,0 +1,172 @@
+package com.stdproject.common;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.stdproject.entity.AppOptLog;
+import com.stdproject.service.IAppOptLogService;
+import com.stdproject.utils.JwtUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+
+/**
+ * 操作日志切面
+ *
+ * @author StdProject
+ */
+@Aspect
+@Component
+@Slf4j
+public class OperationLogAspect {
+
+ @Autowired
+ private IAppOptLogService appOptLogService;
+
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * 定义切点
+ */
+ @Pointcut("@annotation(com.stdproject.common.OperationLog)")
+ public void operationLogPointcut() {
+ }
+
+ /**
+ * 正常返回通知
+ */
+ @AfterReturning(pointcut = "operationLogPointcut()", returning = "result")
+ public void doAfterReturning(JoinPoint joinPoint, Object result) {
+ handleLog(joinPoint, null, result);
+ }
+
+ /**
+ * 异常通知
+ */
+ @AfterThrowing(pointcut = "operationLogPointcut()", throwing = "e")
+ public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
+ handleLog(joinPoint, e, null);
+ }
+
+ /**
+ * 处理日志记录
+ */
+ private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
+ try {
+ // 获取注解信息
+ OperationLog operationLog = getAnnotationLog(joinPoint);
+ if (operationLog == null) {
+ return;
+ }
+
+ // 获取请求信息
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ if (attributes == null) {
+ return;
+ }
+ HttpServletRequest request = attributes.getRequest();
+
+ // 创建操作日志对象
+ AppOptLog optLog = new AppOptLog();
+
+ // 设置基本信息
+ optLog.setOpttype(operationLog.type());
+ optLog.setModule(operationLog.module());
+ optLog.setDescription(operationLog.description());
+ optLog.setMethod(request.getMethod() + " " + request.getRequestURI());
+ optLog.setRequestip(getIpAddress(request));
+ optLog.setBrowser(request.getHeader("User-Agent"));
+ optLog.setLogtime(LocalDateTime.now());
+
+ // 设置操作用户
+ String token = request.getHeader("Authorization");
+ if (token != null && token.startsWith("Bearer ")) {
+ token = token.substring(7);
+ try {
+ String username = jwtUtils.getUsernameFromToken(token);
+ optLog.setUsername(username);
+ } catch (Exception ex) {
+ log.warn("解析JWT token失败: {}", ex.getMessage());
+ }
+ }
+
+ // 设置请求参数
+ if (operationLog.recordParams()) {
+ String methodParams = getMethodParams(joinPoint);
+ optLog.setParams(methodParams);
+ }
+
+ // 保存日志
+ appOptLogService.save(optLog);
+
+ } catch (Exception ex) {
+ log.error("记录操作日志异常: {}", ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * 获取注解信息
+ */
+ private OperationLog getAnnotationLog(JoinPoint joinPoint) {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ Method method = signature.getMethod();
+ return method.getAnnotation(OperationLog.class);
+ }
+
+ /**
+ * 获取方法参数
+ */
+ private String getMethodParams(JoinPoint joinPoint) {
+ try {
+ Object[] args = joinPoint.getArgs();
+ if (args == null || args.length == 0) {
+ return "";
+ }
+ String params = objectMapper.writeValueAsString(Arrays.asList(args));
+ if (params.length() > 2000) {
+ params = params.substring(0, 2000) + "...";
+ }
+ return params;
+ } catch (Exception e) {
+ return "参数序列化失败";
+ }
+ }
+
+ /**
+ * 获取客户端IP地址
+ */
+ private String getIpAddress(HttpServletRequest request) {
+ String ip = request.getHeader("X-Forwarded-For");
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("Proxy-Client-IP");
+ }
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("WL-Proxy-Client-IP");
+ }
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("HTTP_CLIENT_IP");
+ }
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+ }
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+ ip = request.getRemoteAddr();
+ }
+ return ip;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/PageRequest.java b/backend/src/main/java/com/stdproject/common/PageRequest.java
new file mode 100644
index 0000000..8495a86
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/PageRequest.java
@@ -0,0 +1,72 @@
+package com.stdproject.common;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+
+
+/**
+ * 分页查询请求类
+ *
+ * @author StdProject
+ */
+@Data
+public class PageRequest {
+
+ /**
+ * 当前页码,从1开始
+ */
+ @NotNull(message = "页码不能为空")
+ @Min(value = 1, message = "页码必须大于0") @Min(value = 1, message = "页码必须大于0")
+ private Integer current = 1;
+
+ /**
+ * 每页大小
+ */
+ @NotNull(message = "每页大小不能为空")
+ @Min(value = 1, message = "每页大小必须大于0")
+ private Integer size = 20;
+
+ /**
+ * 排序字段
+ */
+ private String orderBy;
+
+ /**
+ * 排序方向:asc-升序,desc-降序
+ */
+ private String orderDirection = "desc";
+
+ /**
+ * 搜索关键字
+ */
+ private String keyword;
+
+ /**
+ * 获取偏移量
+ *
+ * @return 偏移量
+ */
+ public Integer getOffset() {
+ return (current - 1) * size;
+ }
+
+ /**
+ * 是否升序排序
+ *
+ * @return 是否升序
+ */
+ public boolean isAsc() {
+ return "asc".equalsIgnoreCase(orderDirection);
+ }
+
+ /**
+ * 是否降序排序
+ *
+ * @return 是否降序
+ */
+ public boolean isDesc() {
+ return "desc".equalsIgnoreCase(orderDirection);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/Result.java b/backend/src/main/java/com/stdproject/common/Result.java
new file mode 100644
index 0000000..3861e5e
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/Result.java
@@ -0,0 +1,191 @@
+package com.stdproject.common;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 统一响应结果类
+ *
+ * @author StdProject
+ * @param 数据类型
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Result implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 响应码
+ */
+ private Integer code;
+
+ /**
+ * 响应消息
+ */
+ private String message;
+
+ /**
+ * 响应数据
+ */
+ private T data;
+
+ /**
+ * 时间戳
+ */
+ private Long timestamp;
+
+ public Result() {
+ this.timestamp = System.currentTimeMillis();
+ }
+
+ public Result(Integer code, String message) {
+ this();
+ this.code = code;
+ this.message = message;
+ }
+
+ public Result(Integer code, String message, T data) {
+ this(code, message);
+ this.data = data;
+ }
+
+ /**
+ * 成功响应
+ *
+ * @param 数据类型
+ * @return 成功响应
+ */
+ public static Result success() {
+ return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
+ }
+
+ /**
+ * 成功响应
+ *
+ * @param data 响应数据
+ * @param 数据类型
+ * @return 成功响应
+ */
+ public static Result success(T data) {
+ return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
+ }
+
+ /**
+ * 成功响应
+ *
+ * @param message 响应消息
+ * @param data 响应数据
+ * @param 数据类型
+ * @return 成功响应
+ */
+ public static Result success(String message, T data) {
+ return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
+ }
+
+ /**
+ * 失败响应
+ *
+ * @param 数据类型
+ * @return 失败响应
+ */
+ public static Result error() {
+ return new Result<>(ResultCode.ERROR.getCode(), ResultCode.ERROR.getMessage());
+ }
+
+ /**
+ * 失败响应
+ *
+ * @param message 错误消息
+ * @param 数据类型
+ * @return 失败响应
+ */
+ public static Result error(String message) {
+ return new Result<>(ResultCode.ERROR.getCode(), message);
+ }
+
+ /**
+ * 失败响应
+ *
+ * @param code 错误码
+ * @param message 错误消息
+ * @param 数据类型
+ * @return 失败响应
+ */
+ public static Result error(Integer code, String message) {
+ return new Result<>(code, message);
+ }
+
+ /**
+ * 失败响应
+ *
+ * @param resultCode 结果码枚举
+ * @param 数据类型
+ * @return 失败响应
+ */
+ public static Result error(ResultCode resultCode) {
+ return new Result<>(resultCode.getCode(), resultCode.getMessage());
+ }
+
+ /**
+ * 未授权响应
+ *
+ * @param 数据类型
+ * @return 未授权响应
+ */
+ public static Result unauthorized() {
+ return new Result<>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage());
+ }
+
+ /**
+ * 禁止访问响应
+ *
+ * @param 数据类型
+ * @return 禁止访问响应
+ */
+ public static Result forbidden() {
+ return new Result<>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage());
+ }
+
+ /**
+ * 资源未找到响应
+ *
+ * @param 数据类型
+ * @return 资源未找到响应
+ */
+ public static Result notFound() {
+ return new Result<>(ResultCode.NOT_FOUND.getCode(), ResultCode.NOT_FOUND.getMessage());
+ }
+
+ /**
+ * 参数错误响应
+ *
+ * @param 数据类型
+ * @return 参数错误响应
+ */
+ public static Result badRequest() {
+ return new Result<>(ResultCode.BAD_REQUEST.getCode(), ResultCode.BAD_REQUEST.getMessage());
+ }
+
+ /**
+ * 参数错误响应
+ *
+ * @param message 错误消息
+ * @param 数据类型
+ * @return 参数错误响应
+ */
+ public static Result badRequest(String message) {
+ return new Result<>(ResultCode.BAD_REQUEST.getCode(), message);
+ }
+
+ /**
+ * 判断是否成功
+ *
+ * @return 是否成功
+ */
+ public boolean isSuccess() {
+ return ResultCode.SUCCESS.getCode().equals(this.code);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/common/ResultCode.java b/backend/src/main/java/com/stdproject/common/ResultCode.java
new file mode 100644
index 0000000..f985514
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/common/ResultCode.java
@@ -0,0 +1,223 @@
+package com.stdproject.common;
+
+/**
+ * 结果码枚举
+ *
+ * @author StdProject
+ */
+public enum ResultCode {
+
+ /**
+ * 成功
+ */
+ SUCCESS(200, "操作成功"),
+
+ /**
+ * 失败
+ */
+ ERROR(500, "操作失败"),
+
+ /**
+ * 参数错误
+ */
+ BAD_REQUEST(400, "参数错误"),
+
+ /**
+ * 未授权
+ */
+ UNAUTHORIZED(401, "未授权"),
+
+ /**
+ * 禁止访问
+ */
+ FORBIDDEN(403, "禁止访问"),
+
+ /**
+ * 资源未找到
+ */
+ NOT_FOUND(404, "资源未找到"),
+
+ /**
+ * 方法不允许
+ */
+ METHOD_NOT_ALLOWED(405, "方法不允许"),
+
+ /**
+ * 请求超时
+ */
+ REQUEST_TIMEOUT(408, "请求超时"),
+
+ /**
+ * 用户名或密码错误
+ */
+ LOGIN_ERROR(1001, "用户名或密码错误"),
+
+ /**
+ * 用户已被禁用
+ */
+ USER_DISABLED(1002, "用户已被禁用"),
+
+ /**
+ * 用户已被锁定
+ */
+ USER_LOCKED(1003, "用户已被锁定"),
+
+ /**
+ * 密码已过期
+ */
+ PASSWORD_EXPIRED(1004, "密码已过期"),
+
+ /**
+ * 登录失败次数过多
+ */
+ LOGIN_FAILED_TOO_MANY(1005, "登录失败次数过多,账户已被锁定"),
+
+ /**
+ * Token无效
+ */
+ TOKEN_INVALID(1006, "Token无效"),
+
+ /**
+ * Token已过期
+ */
+ TOKEN_EXPIRED(1007, "Token已过期"),
+
+ /**
+ * 用户不存在
+ */
+ USER_NOT_FOUND(1008, "用户不存在"),
+
+ /**
+ * 用户名已存在
+ */
+ USERNAME_EXISTS(1009, "用户名已存在"),
+
+ /**
+ * 邮箱已存在
+ */
+ EMAIL_EXISTS(1010, "邮箱已存在"),
+
+ /**
+ * 手机号已存在
+ */
+ PHONE_EXISTS(1011, "手机号已存在"),
+
+ /**
+ * 角色不存在
+ */
+ ROLE_NOT_FOUND(1012, "角色不存在"),
+
+ /**
+ * 角色已存在
+ */
+ ROLE_EXISTS(1013, "角色已存在"),
+
+ /**
+ * 菜单不存在
+ */
+ MENU_NOT_FOUND(1014, "菜单不存在"),
+
+ /**
+ * 菜单已存在
+ */
+ MENU_EXISTS(1015, "菜单已存在"),
+
+ /**
+ * 组织不存在
+ */
+ ORGANIZATION_NOT_FOUND(1016, "组织不存在"),
+
+ /**
+ * 组织已存在
+ */
+ ORGANIZATION_EXISTS(1017, "组织已存在"),
+
+ /**
+ * 数据字典不存在
+ */
+ DICTIONARY_NOT_FOUND(1018, "数据字典不存在"),
+
+ /**
+ * 数据字典已存在
+ */
+ DICTIONARY_EXISTS(1019, "数据字典已存在"),
+
+ /**
+ * 文件上传失败
+ */
+ FILE_UPLOAD_ERROR(2001, "文件上传失败"),
+
+ /**
+ * 文件下载失败
+ */
+ FILE_DOWNLOAD_ERROR(2002, "文件下载失败"),
+
+ /**
+ * 文件不存在
+ */
+ FILE_NOT_FOUND(2003, "文件不存在"),
+
+ /**
+ * 文件格式不支持
+ */
+ FILE_FORMAT_NOT_SUPPORTED(2004, "文件格式不支持"),
+
+ /**
+ * 文件大小超出限制
+ */
+ FILE_SIZE_EXCEEDED(2005, "文件大小超出限制"),
+
+ /**
+ * 数据库操作失败
+ */
+ DATABASE_ERROR(3001, "数据库操作失败"),
+
+ /**
+ * 数据不存在
+ */
+ DATA_NOT_FOUND(3002, "数据不存在"),
+
+ /**
+ * 数据已存在
+ */
+ DATA_EXISTS(3003, "数据已存在"),
+
+ /**
+ * 数据完整性约束违反
+ */
+ DATA_INTEGRITY_VIOLATION(3004, "数据完整性约束违反"),
+
+ /**
+ * 系统繁忙
+ */
+ SYSTEM_BUSY(9001, "系统繁忙,请稍后重试"),
+
+ /**
+ * 系统维护中
+ */
+ SYSTEM_MAINTENANCE(9002, "系统维护中"),
+ /**
+ * 系统维护中
+ */
+
+ /**
+ * 未知错误
+ */
+ UNKNOWN_ERROR(9999, "未知错误");
+
+ private final Integer code;
+ private final String message;
+
+ ResultCode(Integer code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/CacheConfig.java b/backend/src/main/java/com/stdproject/config/CacheConfig.java
new file mode 100644
index 0000000..3d4f348
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/CacheConfig.java
@@ -0,0 +1,26 @@
+package com.stdproject.config;
+
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 缓存配置类
+ *
+ * @author StdProject
+ */
+@Configuration
+@EnableCaching
+public class CacheConfig {
+
+ // 缓存配置主要通过application.yml中的spring.cache.jcache.config配置
+ // 这里启用缓存注解支持
+
+ /**
+ * 缓存名称常量
+ */
+ public static final String USER_CACHE = "userCache";
+ public static final String ROLE_CACHE = "roleCache";
+ public static final String MENU_CACHE = "menuCache";
+ public static final String ORGANIZATION_CACHE = "organizationCache";
+ public static final String DICTIONARY_CACHE = "dictionaryCache";
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java b/backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java
new file mode 100644
index 0000000..e02184b
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java
@@ -0,0 +1,210 @@
+package com.stdproject.config;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.stdproject.entity.AppUser;
+import com.stdproject.entity.AppRole;
+import com.stdproject.entity.AppRoleUser;
+import com.stdproject.entity.AppRoleMenu;
+import com.stdproject.entity.AppMenu;
+import com.stdproject.service.IAppUserService;
+import com.stdproject.service.IAppRoleService;
+import com.stdproject.service.IAppRoleUserService;
+import com.stdproject.service.IAppRoleMenuService;
+import com.stdproject.service.IAppMenuService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+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 org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+/**
+ * 自定义用户详情服务
+ * 实现Spring Security的UserDetailsService接口
+ *
+ * @author StdProject
+ */
+@Slf4j
+@Service
+public class CustomUserDetailsService implements UserDetailsService {
+
+ @Autowired
+ private IAppUserService appUserService;
+
+ @Autowired
+ private IAppRoleService appRoleService;
+
+ @Autowired
+ private IAppRoleUserService appRoleUserService;
+
+ @Autowired
+ private IAppRoleMenuService appRoleMenuService;
+
+ @Autowired
+ private IAppMenuService appMenuService;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ log.debug("加载用户信息: {}", username);
+
+ AppUser appUser = appUserService.findByUsername(username);
+ if (appUser == null) {
+ log.warn("用户不存在: {}", username);
+ throw new UsernameNotFoundException("用户不存在: " + username);
+ }
+
+ // 检查用户状态
+ if (!"1".equals(appUser.getStatus())) {
+ log.warn("用户已被禁用: {}", username);
+ throw new UsernameNotFoundException("用户已被禁用: " + username);
+ }
+
+ // 构建用户权限
+ Collection authorities = buildUserAuthorities(appUser);
+
+ return User.builder()
+ .username(appUser.getUsername())
+ .password(appUser.getPassword())
+ .authorities(authorities)
+ .accountExpired(false)
+ .accountLocked(isAccountLocked(appUser))
+ .credentialsExpired(isCredentialsExpired(appUser))
+ .disabled(!"1".equals(appUser.getStatus()))
+ .build();
+ }
+
+ /**
+ * 构建用户权限
+ *
+ * @param appUser 用户信息
+ * @return 权限集合
+ */
+ private Collection buildUserAuthorities(AppUser appUser) {
+ Set authorities = new HashSet<>();
+
+ try {
+ // 根据用户类型添加基本角色权限
+ if ("0".equals(appUser.getUsertype())) {
+ authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+ } else {
+ authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
+ }
+
+ // 查询用户的角色
+ QueryWrapper roleUserQuery = new QueryWrapper<>();
+ roleUserQuery.eq("userid", appUser.getId());
+ List roleUsers = appRoleUserService.list(roleUserQuery);
+
+ if (!roleUsers.isEmpty()) {
+ // 获取角色ID列表
+ List roleIds = roleUsers.stream()
+ .map(AppRoleUser::getRoleid)
+ .collect(Collectors.toList());
+
+ // 查询角色信息并添加角色权限
+ List roles = appRoleService.listByIds(roleIds);
+ for (AppRole role : roles) {
+ if ("1".equals(role.getIsvaild())) {
+ // 添加角色权限,格式:ROLE_角色编码
+ if (StringUtils.hasText(role.getRolecode())) {
+ authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRolecode().toUpperCase()));
+ }
+ }
+ }
+
+ // 查询角色关联的菜单权限
+ QueryWrapper roleMenuQuery = new QueryWrapper<>();
+ roleMenuQuery.in("roleid", roleIds);
+ List roleMenus = appRoleMenuService.list(roleMenuQuery);
+
+ if (!roleMenus.isEmpty()) {
+ // 获取菜单ID列表
+ List menuIds = roleMenus.stream()
+ .map(AppRoleMenu::getMenuid)
+ .distinct()
+ .collect(Collectors.toList());
+
+ // 查询菜单信息并添加菜单权限
+ List menus = appMenuService.listByIds(menuIds);
+ for (AppMenu menu : menus) {
+ if ("1".equals(menu.getIsdisplay()) && StringUtils.hasText(menu.getCode())) {
+ // 添加菜单权限,格式:菜单编码
+ authorities.add(new SimpleGrantedAuthority(menu.getCode()));
+
+ // 根据菜单类型添加操作权限
+ String menuCode = menu.getCode();
+ if (StringUtils.hasText(menuCode)) {
+ // 为每个菜单添加基本操作权限
+ authorities.add(new SimpleGrantedAuthority(menuCode + ":list"));
+ authorities.add(new SimpleGrantedAuthority(menuCode + ":detail"));
+
+ // 管理员拥有所有操作权限
+ if ("0".equals(appUser.getUsertype())) {
+ authorities.add(new SimpleGrantedAuthority(menuCode + ":add"));
+ authorities.add(new SimpleGrantedAuthority(menuCode + ":edit"));
+ authorities.add(new SimpleGrantedAuthority(menuCode + ":delete"));
+ authorities.add(new SimpleGrantedAuthority(menuCode + ":permission"));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ log.debug("用户 {} 的权限列表: {}", appUser.getUsername(),
+ authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
+
+ } catch (Exception e) {
+ log.error("构建用户权限失败: {}", e.getMessage(), e);
+ // 发生异常时,至少保证基本角色权限
+ authorities.clear();
+ if ("0".equals(appUser.getUsertype())) {
+ authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+ } else {
+ authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
+ }
+ }
+
+ return authorities;
+ }
+
+ /**
+ * 检查账户是否被锁定
+ *
+ * @param appUser 用户信息
+ * @return 是否被锁定
+ */
+ private boolean isAccountLocked(AppUser appUser) {
+ // 检查登录失败锁定时间
+ if (appUser.getFailedlocktime() != null) {
+ // 如果锁定时间还未过期,则账户被锁定
+ return appUser.getFailedlocktime().isAfter(java.time.LocalDateTime.now());
+ }
+ return false;
+ }
+
+ /**
+ * 检查凭证是否过期
+ *
+ * @param appUser 用户信息
+ * @return 是否过期
+ */
+ private boolean isCredentialsExpired(AppUser appUser) {
+ // 检查密码是否过期
+ if (appUser.getPwdresettime() != null && appUser.getPwdvalidperiod() != null) {
+ java.time.LocalDateTime expireTime = appUser.getPwdresettime().plusDays(appUser.getPwdvalidperiod());
+ return expireTime.isBefore(java.time.LocalDateTime.now());
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/JwtAuthenticationEntryPoint.java b/backend/src/main/java/com/stdproject/config/JwtAuthenticationEntryPoint.java
new file mode 100644
index 0000000..1688df9
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/JwtAuthenticationEntryPoint.java
@@ -0,0 +1,43 @@
+package com.stdproject.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.stdproject.common.Result;
+import com.stdproject.common.ResultCode;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+
+import java.io.IOException;
+
+/**
+ * JWT认证入口点
+ * 处理未认证的请求
+ *
+ * @author StdProject
+ */
+@Slf4j
+@Component
+public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
+ throws IOException, ServletException {
+
+ log.warn("未认证访问: {} - {}", request.getRequestURI(), authException.getMessage());
+
+ response.setContentType("application/json;charset=UTF-8");
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+
+ Result result = Result.error(ResultCode.UNAUTHORIZED);
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ String jsonResult = objectMapper.writeValueAsString(result);
+
+ response.getWriter().write(jsonResult);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java b/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..a077d7e
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java
@@ -0,0 +1,78 @@
+package com.stdproject.config;
+
+import com.stdproject.utils.JwtUtils;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+
+import java.io.IOException;
+
+/**
+ * JWT认证过滤器
+ *
+ * @author StdProject
+ */
+@Slf4j
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ try {
+ String jwt = getJwtFromRequest(request);
+
+ if (StringUtils.hasText(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
+ String username = jwtUtils.getUsernameFromToken(jwt);
+
+ if (StringUtils.hasText(username)) {
+ UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+
+ if (jwtUtils.validateToken(jwt, userDetails.getUsername())) {
+ UsernamePasswordAuthenticationToken authentication =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("无法设置用户认证: {}", e.getMessage());
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ /**
+ * 从请求中获取JWT令牌
+ *
+ * @param request HTTP请求
+ * @return JWT令牌
+ */
+ private String getJwtFromRequest(HttpServletRequest request) {
+ String bearerToken = request.getHeader("Authorization");
+ if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/MybatisPlusConfig.java b/backend/src/main/java/com/stdproject/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..5096d9d
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/MybatisPlusConfig.java
@@ -0,0 +1,40 @@
+package com.stdproject.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+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;
+
+/**
+ * MyBatis-Plus配置类
+ *
+ * @author StdProject
+ */
+@Configuration
+@MapperScan("com.stdproject.mapper")
+public class MybatisPlusConfig {
+
+ /**
+ * MyBatis-Plus拦截器配置
+ */
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+
+ // 分页插件
+ PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
+ // 设置最大单页限制数量,默认 500 条,-1 不受限制
+ paginationInnerInterceptor.setMaxLimit(1000L);
+ // 溢出总页数后是否进行处理
+ paginationInnerInterceptor.setOverflow(false);
+ interceptor.addInnerInterceptor(paginationInnerInterceptor);
+
+ // 乐观锁插件
+ interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+
+ return interceptor;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/PasswordEncoderConfig.java b/backend/src/main/java/com/stdproject/config/PasswordEncoderConfig.java
new file mode 100644
index 0000000..1683e2d
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/PasswordEncoderConfig.java
@@ -0,0 +1,21 @@
+package com.stdproject.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+/**
+ * 密码编码器配置类
+ *
+ * @author StdProject
+ */
+@Configuration
+public class PasswordEncoderConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+
+ return new BCryptPasswordEncoder();
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/SecurityConfig.java b/backend/src/main/java/com/stdproject/config/SecurityConfig.java
new file mode 100644
index 0000000..1f6dc64
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/SecurityConfig.java
@@ -0,0 +1,172 @@
+package com.stdproject.config;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+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.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Spring Security配置类
+ *
+ * @author StdProject
+ */
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity(prePostEnabled = true)
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
+ private final JwtAuthenticationFilter jwtAuthenticationFilter;
+ private final CustomUserDetailsService customUserDetailsService;
+
+ @Value("${spring.security.jwt.enabled:true}")
+ private boolean jwtEnabled;
+
+ @Value("${spring.security.cors.allowed-origins:http://localhost:3000,http://localhost:8080}")
+ private List allowedOrigins;
+
+ @Value("${spring.security.cors.max-age:3600}")
+ private Long corsMaxAge;
+
+ // 公开路径配置
+ private static final String[] PUBLIC_PATHS = {
+ "/api/auth/**",
+ "/api/public/**",
+ "/swagger-ui/**",
+ "/v3/api-docs/**",
+ "/swagger-ui.html",
+ "/webjars/**",
+ "/favicon.ico",
+ "/error",
+ "/actuator/health",
+ "/actuator/info"
+ };
+
+
+ /**
+ * 认证管理器
+ */
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+
+ /**
+ * CORS配置
+ * 更安全的CORS配置,支持配置化的允许源
+ */
+ @Bean
+ public CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+
+ // 设置允许的源,支持配置化
+ configuration.setAllowedOrigins(allowedOrigins);
+
+ // 设置允许的HTTP方法
+ configuration.setAllowedMethods(Arrays.asList(
+ "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
+ ));
+
+ // 设置允许的请求头
+ configuration.setAllowedHeaders(Arrays.asList(
+ "Authorization", "Content-Type", "X-Requested-With",
+ "Accept", "Origin", "Access-Control-Request-Method",
+ "Access-Control-Request-Headers", "X-CSRF-TOKEN"
+ ));
+
+ // 设置暴露的响应头
+ configuration.setExposedHeaders(Arrays.asList(
+ "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials",
+ "Authorization", "Content-Disposition"
+ ));
+
+ configuration.setAllowCredentials(true);
+ configuration.setMaxAge(corsMaxAge);
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+
+ /**
+ * 安全过滤器链配置
+ * 增强的安全配置,包含安全头和更完善的认证授权
+ */
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ // CORS配置
+ .cors(cors -> cors.configurationSource(corsConfigurationSource()))
+
+ // 禁用CSRF(因为使用JWT,无状态)
+ .csrf(csrf -> csrf.disable())
+
+ // 会话管理配置
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .maximumSessions(1)
+ .maxSessionsPreventsLogin(false)
+ )
+
+ // 异常处理配置
+ .exceptionHandling(ex -> ex
+ .authenticationEntryPoint(jwtAuthenticationEntryPoint)
+ )
+
+ // 安全头配置
+ .headers(headers -> headers
+ // 禁用iframe嵌入,防止点击劫持攻击
+ .frameOptions().deny()
+ // 设置X-Content-Type-Options头为nosniff,防止MIME类型嗅探攻击
+ .contentTypeOptions().and()
+ // 设置Referrer Policy为strict-origin-when-cross-origin
+ // 跨域请求时只发送源(origin),同源请求发送完整referrer
+ .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
+ // 注意:已移除HSTS配置,不再强制使用HTTPS
+ );
+
+ // 根据配置决定是否启用JWT认证
+ if (jwtEnabled) {
+ http.authorizeHttpRequests(authz -> authz
+ // 公开接口,无需认证
+ .requestMatchers(PUBLIC_PATHS).permitAll()
+ // 管理员接口需要管理员权限
+ .requestMatchers("/api/admin/**").hasRole("ADMIN")
+ // 其他所有请求都需要认证
+ .anyRequest().authenticated()
+ );
+
+ // 添加JWT过滤器
+ http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+ } else {
+ // 如果禁用JWT认证,则允许所有请求(开发环境)
+ http.authorizeHttpRequests(authz -> authz
+ .anyRequest().permitAll()
+ );
+ }
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/SwaggerConfig.java b/backend/src/main/java/com/stdproject/config/SwaggerConfig.java
new file mode 100644
index 0000000..88437e3
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/SwaggerConfig.java
@@ -0,0 +1,46 @@
+package com.stdproject.config;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Swagger配置类
+ *
+ * @author StdProject
+ */
+@Configuration
+public class SwaggerConfig {
+
+ /**
+ * OpenAPI配置
+ */
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(new Info()
+ .title("StdProject API")
+ .version("1.0.0")
+ .description("标准项目后端API文档")
+ .contact(new Contact()
+ .name("StdProject Team")
+ .email("support@stdproject.com")
+ .url("https://www.stdproject.com"))
+ .license(new License()
+ .name("Apache 2.0")
+ .url("https://www.apache.org/licenses/LICENSE-2.0")))
+ .components(new Components()
+ .addSecuritySchemes("Bearer", new SecurityScheme()
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("JWT认证,请在值前面加上'Bearer '前缀")))
+ .addSecurityItem(new SecurityRequirement().addList("Bearer"));
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/config/WebConfig.java b/backend/src/main/java/com/stdproject/config/WebConfig.java
new file mode 100644
index 0000000..e207fd0
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/config/WebConfig.java
@@ -0,0 +1,28 @@
+package com.stdproject.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Web配置类
+ *
+ * @author StdProject
+ */
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ /**
+ * 静态资源处理
+ */
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ // Swagger UI 静态资源
+ registry.addResourceHandler("/swagger-ui/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
+
+ // 其他静态资源
+ registry.addResourceHandler("/static/**")
+ .addResourceLocations("classpath:/static/");
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java b/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java
new file mode 100644
index 0000000..bbce2a5
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java
@@ -0,0 +1,270 @@
+package com.stdproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.stdproject.common.OperationLog;
+import com.stdproject.common.PageRequest;
+import com.stdproject.common.Result;
+import com.stdproject.entity.AppDictionary;
+import com.stdproject.service.IAppDictionaryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * 应用-数据字典表 前端控制器
+ *
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@Tag(name = "数据字典管理", description = "数据字典添加、数据字典修改、数据字典删除、数据字典查询等功能")
+@RestController
+@RequestMapping("/app-dictionary")
+public class AppDictionaryController {
+
+ @Autowired
+ private IAppDictionaryService appDictionaryService;
+
+ @Operation(summary = "分页查询数据字典列表")
+ @PostMapping("/page")
+ @OperationLog(type = "06", module = "数据字典管理", description = "分页查询数据字典列表")
+ public Result> page(@RequestBody @Valid PageRequest pageRequest) {
+ Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+
+ // 关键字搜索
+ if (StringUtils.hasText(pageRequest.getKeyword())) {
+ queryWrapper.and(wrapper -> wrapper
+ .like("dictname", pageRequest.getKeyword())
+ .or().like("dictcode", pageRequest.getKeyword())
+ .or().like("dictdata", pageRequest.getKeyword())
+ );
+ }
+
+ // 排序
+ if (StringUtils.hasText(pageRequest.getOrderBy())) {
+ if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
+ queryWrapper.orderByAsc(pageRequest.getOrderBy());
+ } else {
+ queryWrapper.orderByDesc(pageRequest.getOrderBy());
+ }
+ } else {
+ queryWrapper.orderByAsc("dictcode", "orderno");
+ }
+
+ IPage result = appDictionaryService.page(page, queryWrapper);
+ return Result.success(result);
+ }
+
+ @Operation(summary = "查询所有数据字典列表")
+ @GetMapping("/list")
+ @OperationLog(type = "06", module = "数据字典管理", description = "查询所有数据字典列表")
+ public Result> list() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.orderByAsc("dictcode", "orderno");
+
+ List dictionaries = appDictionaryService.list(queryWrapper);
+ return Result.success(dictionaries);
+ }
+
+ @Operation(summary = "根据ID查询数据字典详情")
+ @GetMapping("/{id}")
+ @OperationLog(type = "06", module = "数据字典管理", description = "查询数据字典详情")
+ public Result getById(@Parameter(description = "字典ID") @PathVariable String id) {
+ AppDictionary dictionary = appDictionaryService.getById(id);
+ return Result.success(dictionary);
+ }
+
+ @Operation(summary = "根据字典编码查询字典项列表")
+ @GetMapping("/code/{dictCode}")
+ @OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码查询字典项列表")
+ public Result> getByDictCode(@Parameter(description = "字典编码") @PathVariable String dictCode) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("dictcode", dictCode);
+ queryWrapper.orderByAsc("orderno");
+
+ List dictionaries = appDictionaryService.list(queryWrapper);
+ return Result.success(dictionaries);
+ }
+
+ @Operation(summary = "根据字典编码和字典值查询字典项")
+ @GetMapping("/code/{dictCode}/data/{dictData}")
+ @OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码和字典数据查询字典项")
+ public Result getByCodeAndData(
+ @Parameter(description = "字典编码") @PathVariable String dictCode,
+ @Parameter(description = "字典数据") @PathVariable String dictData) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("dictcode", dictCode);
+ queryWrapper.eq("dictdata", dictData);
+
+ AppDictionary dictionary = appDictionaryService.getOne(queryWrapper);
+ return Result.success(dictionary);
+ }
+
+ @Operation(summary = "新增数据字典")
+ @PostMapping
+ @OperationLog(type = "01", module = "数据字典管理", description = "新增数据字典")
+ public Result save(@RequestBody @Valid AppDictionary appDictionary) {
+ // 检查同一字典编码下的字典值是否已存在
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("dictcode", appDictionary.getDictcode());
+ queryWrapper.eq("dictdata", appDictionary.getDictdata());
+ AppDictionary existDictionary = appDictionaryService.getOne(queryWrapper);
+ if (existDictionary != null) {
+ return Result.error("该字典编码下的字典值已存在");
+ }
+
+ // 设置默认值
+ if (appDictionary.getOrderno() == null) {
+ // 获取同一字典编码下的最大序号
+ QueryWrapper orderQuery = new QueryWrapper<>();
+ orderQuery.eq("dictcode", appDictionary.getDictcode());
+ orderQuery.orderByDesc("orderno");
+ orderQuery.last("LIMIT 1");
+ AppDictionary lastDict = appDictionaryService.getOne(orderQuery);
+ appDictionary.setOrderno(lastDict != null ? lastDict.getOrderno() + 1 : 1);
+ }
+
+ appDictionary.setLastmodifydate(LocalDateTime.now());
+ boolean success = appDictionaryService.save(appDictionary);
+ return success ? Result.success("新增成功") : Result.error("新增失败");
+ }
+
+ @Operation(summary = "修改数据字典")
+ @PutMapping
+ @OperationLog(type = "02", module = "数据字典管理", description = "修改数据字典")
+ public Result update(@RequestBody @Valid AppDictionary appDictionary) {
+ // 检查字典是否存在
+ AppDictionary existDictionary = appDictionaryService.getById(appDictionary.getId());
+ if (existDictionary == null) {
+ return Result.error("数据字典不存在");
+ }
+
+ // 如果修改了字典编码或字典值,检查新的组合是否已被其他字典项使用
+ if (!existDictionary.getDictcode().equals(appDictionary.getDictcode()) ||
+ !existDictionary.getDictdata().equals(appDictionary.getDictdata())) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("dictcode", appDictionary.getDictcode());
+ queryWrapper.eq("dictdata", appDictionary.getDictdata());
+ queryWrapper.ne("id", appDictionary.getId());
+ AppDictionary dictWithSameCodeValue = appDictionaryService.getOne(queryWrapper);
+ if (dictWithSameCodeValue != null) {
+ return Result.error("该字典编码下的字典数据已被其他字典项使用");
+ }
+ }
+
+ appDictionary.setLastmodifydate(LocalDateTime.now());
+ boolean success = appDictionaryService.updateById(appDictionary);
+ return success ? Result.success("修改成功") : Result.error("修改失败");
+ }
+
+ @Operation(summary = "删除数据字典")
+ @DeleteMapping("/{id}")
+ @OperationLog(type = "03", module = "数据字典管理", description = "删除数据字典")
+ public Result delete(@Parameter(description = "字典ID") @PathVariable String id) {
+ boolean success = appDictionaryService.removeById(id);
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ @Operation(summary = "批量删除数据字典")
+ @DeleteMapping("/batch")
+ @OperationLog(type = "03", module = "数据字典管理", description = "批量删除数据字典")
+ public Result deleteBatch(@RequestBody List ids) {
+ boolean success = appDictionaryService.removeByIds(ids);
+ return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
+ }
+
+ @Operation(summary = "根据字典编码删除所有字典项")
+ @DeleteMapping("/code/{dictCode}")
+ @OperationLog(type = "03", module = "数据字典管理", description = "根据字典编码删除所有字典项")
+ public Result deleteByDictCode(@Parameter(description = "字典编码") @PathVariable String dictCode) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("dictcode", dictCode);
+
+ boolean success = appDictionaryService.remove(queryWrapper);
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ @Operation(summary = "根据字典名称获取字典数据")
+ @GetMapping("/name/{dictName}")
+ @OperationLog(type = "06", module = "数据字典管理", description = "根据字典名称获取字典数据")
+ public Result getDictDataByName(@Parameter(description = "字典名称") @PathVariable String dictName) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("dictname", dictName);
+ AppDictionary dictionary = appDictionaryService.getOne(queryWrapper);
+
+ if (dictionary != null) {
+ return Result.success(dictionary.getDictdata());
+ }
+ return Result.error("未找到对应的字典数据");
+ }
+
+ @Operation(summary = "获取所有字典编码列表")
+ @GetMapping("/codes")
+ @OperationLog(type = "06", module = "数据字典管理", description = "获取所有字典编码列表")
+ public Result> getDictCodes() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.select("DISTINCT dictcode");
+ queryWrapper.orderByAsc("dictcode");
+
+ List dictionaries = appDictionaryService.list(queryWrapper);
+ List dictCodes = dictionaries.stream()
+ .map(AppDictionary::getDictcode)
+ .distinct()
+ .collect(java.util.stream.Collectors.toList());
+
+ return Result.success(dictCodes);
+ }
+
+ @Operation(summary = "调整字典项顺序")
+ @PutMapping("/reorder")
+ @OperationLog(type = "02", module = "数据字典管理", description = "调整字典项顺序")
+ public Result reorder(@RequestBody List orderRequests) {
+ for (DictOrderRequest request : orderRequests) {
+ AppDictionary dictionary = appDictionaryService.getById(request.getId());
+ if (dictionary != null) {
+ dictionary.setOrderno(request.getOrderno());
+ dictionary.setLastmodifydate(LocalDateTime.now());
+ appDictionaryService.updateById(dictionary);
+ }
+ }
+ return Result.success("顺序调整成功");
+ }
+
+ @Operation(summary = "根据字典编码分组查询字典项")
+ @GetMapping("/grouped")
+ @OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码分组查询字典项")
+ public Result>> getGroupedDictionaries() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.orderByAsc("dictcode", "orderno");
+
+ List allDictionaries = appDictionaryService.list(queryWrapper);
+ java.util.Map> groupedDict = allDictionaries.stream()
+ .collect(java.util.stream.Collectors.groupingBy(AppDictionary::getDictcode));
+
+ return Result.success(groupedDict);
+ }
+
+ /**
+ * 字典排序请求
+ */
+ public static class DictOrderRequest {
+ private String id;
+ private Integer orderno;
+
+ public String getId() { return id; }
+ public void setId(String id) { this.id = id; }
+ public Integer getOrderno() { return orderno; }
+ public void setOrderno(Integer orderno) { this.orderno = orderno; }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/controller/AppMenuController.java b/backend/src/main/java/com/stdproject/controller/AppMenuController.java
new file mode 100644
index 0000000..d8093e9
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/controller/AppMenuController.java
@@ -0,0 +1,378 @@
+package com.stdproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.stdproject.common.OperationLog;
+import com.stdproject.common.PageRequest;
+import com.stdproject.common.Result;
+import com.stdproject.entity.AppMenu;
+import com.stdproject.service.IAppMenuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * 应用-菜单表 前端控制器
+ *
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@Tag(name = "菜单管理", description = "菜单添加、菜单修改、菜单删除、菜单查询等功能")
+@RestController
+@RequestMapping("/app-menu")
+public class AppMenuController {
+
+ @Autowired
+ private IAppMenuService appMenuService;
+
+ @Operation(summary = "分页查询菜单列表")
+ @PostMapping("/page")
+ @OperationLog(type = "06", module = "菜单管理", description = "分页查询菜单列表")
+ public Result> page(@RequestBody @Valid PageRequest pageRequest) {
+ Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+
+ // 关键字搜索
+ if (StringUtils.hasText(pageRequest.getKeyword())) {
+ queryWrapper.and(wrapper -> wrapper
+ .like("name", pageRequest.getKeyword())
+ .or().like("code", pageRequest.getKeyword())
+ .or().like("url", pageRequest.getKeyword())
+ );
+ }
+
+ // 排序
+ if (StringUtils.hasText(pageRequest.getOrderBy())) {
+ if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
+ queryWrapper.orderByAsc(pageRequest.getOrderBy());
+ } else {
+ queryWrapper.orderByDesc(pageRequest.getOrderBy());
+ }
+ } else {
+ queryWrapper.orderByAsc("parentid", "orderno");
+ }
+
+ IPage result = appMenuService.page(page, queryWrapper);
+ return Result.success(result);
+ }
+
+ @Operation(summary = "查询所有菜单列表")
+ @GetMapping("/list")
+ @OperationLog(type = "06", module = "菜单管理", description = "查询所有菜单列表")
+ public Result> list() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.orderByAsc("parentid", "orderno");
+
+ List menus = appMenuService.list(queryWrapper);
+ return Result.success(menus);
+ }
+
+ @Operation(summary = "获取菜单树形结构")
+ @GetMapping("/tree")
+ @OperationLog(type = "06", module = "菜单管理", description = "获取菜单树形结构")
+ public Result> getMenuTree() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.orderByAsc("parentid", "orderno");
+
+ List allMenus = appMenuService.list(queryWrapper);
+ List tree = buildMenuTree(allMenus, "0");
+ return Result.success(tree);
+ }
+
+ @Operation(summary = "根据ID查询菜单详情")
+ @GetMapping("/{id}")
+ @OperationLog(type = "06", module = "菜单管理", description = "查询菜单详情")
+ public Result getById(@Parameter(description = "菜单ID") @PathVariable String id) {
+ AppMenu menu = appMenuService.getById(id);
+ return Result.success(menu);
+ }
+
+ @Operation(summary = "根据父级ID查询子菜单")
+ @GetMapping("/children/{parentId}")
+ @OperationLog(type = "06", module = "菜单管理", description = "根据父级ID查询子菜单")
+ public Result> getChildren(@Parameter(description = "父级菜单ID") @PathVariable String parentId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("parentid", parentId);
+ queryWrapper.orderByAsc("orderno");
+
+ List children = appMenuService.list(queryWrapper);
+ return Result.success(children);
+ }
+
+ @Operation(summary = "新增菜单")
+ @PostMapping
+ @OperationLog(type = "01", module = "菜单管理", description = "新增菜单")
+ public Result save(@RequestBody @Valid AppMenu appMenu) {
+ // 检查菜单编号是否已存在
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("code", appMenu.getCode());
+ AppMenu existMenu = appMenuService.getOne(queryWrapper);
+ if (existMenu != null) {
+ return Result.error("菜单编号已存在");
+ }
+
+ // 检查父级菜单是否存在(如果不是顶级菜单)
+ if (!"0".equals(appMenu.getParentid())) {
+ AppMenu parentMenu = appMenuService.getById(appMenu.getParentid());
+ if (parentMenu == null) {
+ return Result.error("父级菜单不存在");
+ }
+ }
+
+ // 设置默认值
+ if (appMenu.getOrderno() == null) {
+ // 获取同级菜单的最大序号
+ QueryWrapper orderQuery = new QueryWrapper<>();
+ orderQuery.eq("parentid", appMenu.getParentid());
+ orderQuery.orderByDesc("orderno");
+ orderQuery.last("LIMIT 1");
+ AppMenu lastMenu = appMenuService.getOne(orderQuery);
+ appMenu.setOrderno(lastMenu != null ? lastMenu.getOrderno() + 1 : 1);
+ }
+
+ if (!StringUtils.hasText(appMenu.getIsdisplay())) {
+ appMenu.setIsdisplay("1");
+ }
+
+ if (!StringUtils.hasText(appMenu.getIslink())) {
+ appMenu.setIslink("0");
+ }
+
+ if (!StringUtils.hasText(appMenu.getType())) {
+ appMenu.setType("01");
+ }
+
+ appMenu.setLastmodifydate(LocalDateTime.now());
+ boolean success = appMenuService.save(appMenu);
+ return success ? Result.success("新增成功") : Result.error("新增失败");
+ }
+
+ @Operation(summary = "修改菜单")
+ @PutMapping
+ @OperationLog(type = "02", module = "菜单管理", description = "修改菜单")
+ public Result update(@RequestBody @Valid AppMenu appMenu) {
+ // 检查菜单是否存在
+ AppMenu existMenu = appMenuService.getById(appMenu.getId());
+ if (existMenu == null) {
+ return Result.error("菜单不存在");
+ }
+
+ // 如果修改了菜单编号,检查新编号是否已被其他菜单使用
+ if (!existMenu.getCode().equals(appMenu.getCode())) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("code", appMenu.getCode());
+ queryWrapper.ne("id", appMenu.getId());
+ AppMenu menuWithSameCode = appMenuService.getOne(queryWrapper);
+ if (menuWithSameCode != null) {
+ return Result.error("菜单编号已被其他菜单使用");
+ }
+ }
+
+ // 如果修改了父级菜单,检查是否会形成循环引用
+ if (!existMenu.getParentid().equals(appMenu.getParentid())) {
+ if (isCircularReference(appMenu.getId(), appMenu.getParentid())) {
+ return Result.error("不能将菜单移动到自己的子菜单下");
+ }
+
+ // 检查父级菜单是否存在(如果不是顶级菜单)
+ if (!"0".equals(appMenu.getParentid())) {
+ AppMenu parentMenu = appMenuService.getById(appMenu.getParentid());
+ if (parentMenu == null) {
+ return Result.error("父级菜单不存在");
+ }
+ }
+ }
+
+ appMenu.setLastmodifydate(LocalDateTime.now());
+ boolean success = appMenuService.updateById(appMenu);
+ return success ? Result.success("修改成功") : Result.error("修改失败");
+ }
+
+ @Operation(summary = "删除菜单")
+ @DeleteMapping("/{id}")
+ @OperationLog(type = "03", module = "菜单管理", description = "删除菜单")
+ public Result delete(@Parameter(description = "菜单ID") @PathVariable String id) {
+ // 检查是否有子菜单
+ QueryWrapper childQuery = new QueryWrapper<>();
+ childQuery.eq("parentid", id);
+ long childCount = appMenuService.count(childQuery);
+ if (childCount > 0) {
+ return Result.error("存在子菜单,无法删除");
+ }
+
+ boolean success = appMenuService.removeById(id);
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ @Operation(summary = "批量删除菜单")
+ @DeleteMapping("/batch")
+ @OperationLog(type = "03", module = "菜单管理", description = "批量删除菜单")
+ public Result deleteBatch(@RequestBody List ids) {
+ for (String id : ids) {
+ // 检查是否有子菜单
+ QueryWrapper childQuery = new QueryWrapper<>();
+ childQuery.eq("parentid", id);
+ long childCount = appMenuService.count(childQuery);
+ if (childCount > 0) {
+ return Result.error("存在子菜单的菜单无法删除");
+ }
+ }
+
+ boolean success = appMenuService.removeByIds(ids);
+ return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
+ }
+
+ @Operation(summary = "根据菜单类型查询菜单列表")
+ @GetMapping("/type/{type}")
+ @OperationLog(type = "06", module = "菜单管理", description = "根据菜单类型查询菜单列表")
+ public Result> getByType(@Parameter(description = "菜单类型") @PathVariable String type) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("type", type);
+ queryWrapper.orderByAsc("parentid", "orderno");
+
+ List menus = appMenuService.list(queryWrapper);
+ return Result.success(menus);
+ }
+
+ @Operation(summary = "获取显示的菜单树")
+ @GetMapping("/display-tree")
+ @OperationLog(type = "06", module = "菜单管理", description = "获取显示的菜单树")
+ public Result> getDisplayMenuTree() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("isdisplay", "1");
+ queryWrapper.orderByAsc("parentid", "orderno");
+
+ List displayMenus = appMenuService.list(queryWrapper);
+ List tree = buildMenuTree(displayMenus, "0");
+ return Result.success(tree);
+ }
+
+ @Operation(summary = "调整菜单顺序")
+ @PutMapping("/reorder")
+ @OperationLog(type = "02", module = "菜单管理", description = "调整菜单顺序")
+ public Result reorder(@RequestBody List orderRequests) {
+ for (MenuOrderRequest request : orderRequests) {
+ AppMenu menu = appMenuService.getById(request.getId());
+ if (menu != null) {
+ menu.setOrderno(request.getOrderno());
+ menu.setLastmodifydate(LocalDateTime.now());
+ appMenuService.updateById(menu);
+ }
+ }
+ return Result.success("顺序调整成功");
+ }
+
+ /**
+ * 构建菜单树
+ */
+ private List buildMenuTree(List allMenus, String parentId) {
+ return allMenus.stream()
+ .filter(menu -> parentId.equals(menu.getParentid()))
+ .map(menu -> {
+ MenuTreeNode node = new MenuTreeNode();
+ node.setId(menu.getId());
+ node.setCode(menu.getCode());
+ node.setName(menu.getName());
+ node.setIcon(menu.getIcon());
+ node.setUrl(menu.getUrl());
+ node.setType(menu.getType());
+ node.setIslink(menu.getIslink());
+ node.setIsdisplay(menu.getIsdisplay());
+ node.setOrderno(menu.getOrderno());
+ node.setParentid(menu.getParentid());
+ node.setModuleId(menu.getModuleId());
+ node.setChildren(buildMenuTree(allMenus, menu.getId()));
+ return node;
+ })
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 检查是否会形成循环引用
+ */
+ private boolean isCircularReference(String menuId, String newParentId) {
+ if ("0".equals(newParentId)) {
+ return false;
+ }
+
+ if (menuId.equals(newParentId)) {
+ return true;
+ }
+
+ AppMenu parentMenu = appMenuService.getById(newParentId);
+ if (parentMenu == null) {
+ return false;
+ }
+
+ return isCircularReference(menuId, parentMenu.getParentid());
+ }
+
+ /**
+ * 菜单树节点
+ */
+ public static class MenuTreeNode {
+ private String id;
+ private String code;
+ private String name;
+ private String icon;
+ private String url;
+ private String type;
+ private String islink;
+ private String isdisplay;
+ private Integer orderno;
+ private String parentid;
+ private String moduleId;
+ private List children = new ArrayList<>();
+
+ // Getters and Setters
+ public String getId() { return id; }
+ public void setId(String id) { this.id = id; }
+ public String getCode() { return code; }
+ public void setCode(String code) { this.code = code; }
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+ public String getIcon() { return icon; }
+ public void setIcon(String icon) { this.icon = icon; }
+ public String getUrl() { return url; }
+ public void setUrl(String url) { this.url = url; }
+ public String getType() { return type; }
+ public void setType(String type) { this.type = type; }
+ public String getIslink() { return islink; }
+ public void setIslink(String islink) { this.islink = islink; }
+ public String getIsdisplay() { return isdisplay; }
+ public void setIsdisplay(String isdisplay) { this.isdisplay = isdisplay; }
+ public Integer getOrderno() { return orderno; }
+ public void setOrderno(Integer orderno) { this.orderno = orderno; }
+ public String getParentid() { return parentid; }
+ public void setParentid(String parentid) { this.parentid = parentid; }
+ public String getModuleId() { return moduleId; }
+ public void setModuleId(String moduleId) { this.moduleId = moduleId; }
+ public List getChildren() { return children; }
+ public void setChildren(List children) { this.children = children; }
+ }
+
+ /**
+ * 菜单排序请求
+ */
+ public static class MenuOrderRequest {
+ private String id;
+ private Integer orderno;
+
+ public String getId() { return id; }
+ public void setId(String id) { this.id = id; }
+ public Integer getOrderno() { return orderno; }
+ public void setOrderno(Integer orderno) { this.orderno = orderno; }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/controller/AppOptLogController.java b/backend/src/main/java/com/stdproject/controller/AppOptLogController.java
new file mode 100644
index 0000000..dd5a61c
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/controller/AppOptLogController.java
@@ -0,0 +1,248 @@
+package com.stdproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.stdproject.common.OperationLog;
+import com.stdproject.common.PageRequest;
+import com.stdproject.common.Result;
+import com.stdproject.entity.AppOptLog;
+import com.stdproject.service.IAppOptLogService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * 应用-操作日志表 前端控制器
+ *
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@Tag(name = "操作日志管理", description = "操作日志查询、操作日志删除等功能")
+@RestController
+@RequestMapping("/api/optlog")
+public class AppOptLogController {
+
+ @Autowired
+ private IAppOptLogService appOptLogService;
+
+ @Operation(summary = "分页查询操作日志列表")
+ @PostMapping("/page")
+ @OperationLog(type = "06", module = "操作日志管理", description = "分页查询操作日志列表")
+ public Result> page(@RequestBody @Valid PageRequest pageRequest) {
+ Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+
+ // 关键字搜索
+ if (StringUtils.hasText(pageRequest.getKeyword())) {
+ queryWrapper.and(wrapper -> wrapper
+ .like("username", pageRequest.getKeyword())
+ .or().like("module", pageRequest.getKeyword())
+ .or().like("description", pageRequest.getKeyword())
+ );
+ }
+
+ // 排序
+ if (StringUtils.hasText(pageRequest.getOrderBy())) {
+ if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
+ queryWrapper.orderByAsc(pageRequest.getOrderBy());
+ } else {
+ queryWrapper.orderByDesc(pageRequest.getOrderBy());
+ }
+ } else {
+ queryWrapper.orderByDesc("logtime");
+ }
+
+ IPage result = appOptLogService.page(page, queryWrapper);
+ return Result.success(result);
+ }
+
+ @Operation(summary = "根据ID查询操作日志详情")
+ @GetMapping("/{id}")
+ @OperationLog(type = "06", module = "操作日志管理", description = "查询操作日志详情")
+ public Result getById(@Parameter(description = "日志ID") @PathVariable String id) {
+ AppOptLog optLog = appOptLogService.getById(id);
+ return Result.success(optLog);
+ }
+
+ @Operation(summary = "根据用户名查询操作日志")
+ @GetMapping("/user/{username}")
+ @OperationLog(type = "06", module = "操作日志管理", description = "根据用户名查询操作日志")
+ public Result> getByUsername(
+ @Parameter(description = "用户名") @PathVariable String username,
+ @Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("username", username);
+ queryWrapper.orderByDesc("logtime");
+ queryWrapper.last("LIMIT " + limit);
+
+ List logs = appOptLogService.list(queryWrapper);
+ return Result.success(logs);
+ }
+
+ @Operation(summary = "根据操作类型查询操作日志")
+ @GetMapping("/type/{type}")
+ @OperationLog(type = "06", module = "操作日志管理", description = "根据操作类型查询操作日志")
+ public Result> getByType(
+ @Parameter(description = "操作类型") @PathVariable String type,
+ @Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("type", type);
+ queryWrapper.orderByDesc("logtime");
+ queryWrapper.last("LIMIT " + limit);
+
+ List logs = appOptLogService.list(queryWrapper);
+ return Result.success(logs);
+ }
+
+ @Operation(summary = "根据模块查询操作日志")
+ @GetMapping("/module/{module}")
+ @OperationLog(type = "06", module = "操作日志管理", description = "根据模块查询操作日志")
+ public Result> getByModule(
+ @Parameter(description = "模块名称") @PathVariable String module,
+ @Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("module", module);
+ queryWrapper.orderByDesc("opttime");
+ queryWrapper.last("LIMIT " + limit);
+
+ List logs = appOptLogService.list(queryWrapper);
+ return Result.success(logs);
+ }
+
+ @Operation(summary = "根据时间范围查询操作日志")
+ @GetMapping("/time-range")
+ @OperationLog(type = "06", module = "操作日志管理", description = "根据时间范围查询操作日志")
+ public Result> getByTimeRange(
+ @Parameter(description = "开始时间") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
+ @Parameter(description = "结束时间") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
+ @Parameter(description = "限制条数") @RequestParam(defaultValue = "1000") Integer limit) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.between("opttime", startTime, endTime);
+ queryWrapper.orderByDesc("opttime");
+ queryWrapper.last("LIMIT " + limit);
+
+ List logs = appOptLogService.list(queryWrapper);
+ return Result.success(logs);
+ }
+
+ @Operation(summary = "根据IP地址查询操作日志")
+ @GetMapping("/ip/{ip}")
+ @OperationLog(type = "06", module = "操作日志管理", description = "根据IP地址查询操作日志")
+ public Result> getByIp(
+ @Parameter(description = "IP地址") @PathVariable String ip,
+ @Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("ip", ip);
+ queryWrapper.orderByDesc("opttime");
+ queryWrapper.last("LIMIT " + limit);
+
+ List logs = appOptLogService.list(queryWrapper);
+ return Result.success(logs);
+ }
+
+
+ @Operation(summary = "删除操作日志")
+ @DeleteMapping("/{id}")
+ @OperationLog(type = "03", module = "操作日志管理", description = "删除操作日志")
+ public Result delete(@Parameter(description = "日志ID") @PathVariable String id) {
+ boolean success = appOptLogService.removeById(id);
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ @Operation(summary = "批量删除操作日志")
+ @DeleteMapping("/batch")
+ @OperationLog(type = "03", module = "操作日志管理", description = "批量删除操作日志")
+ public Result deleteBatch(@RequestBody List ids) {
+ boolean success = appOptLogService.removeByIds(ids);
+ return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
+ }
+
+ @Operation(summary = "清理指定天数前的操作日志")
+ @DeleteMapping("/clean/{days}")
+ @OperationLog(type = "03", module = "操作日志管理", description = "清理历史操作日志")
+ public Result cleanOldLogs(@Parameter(description = "保留天数") @PathVariable Integer days) {
+ LocalDateTime cutoffTime = LocalDateTime.now().minusDays(days);
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.lt("logtime", cutoffTime);
+
+ boolean success = appOptLogService.remove(queryWrapper);
+ return success ? Result.success("清理成功") : Result.error("清理失败");
+ }
+
+ @Operation(summary = "获取操作日志统计信息")
+ @GetMapping("/statistics")
+ @OperationLog(type = "06", module = "操作日志管理", description = "获取操作日志统计信息")
+ public Result getStatistics() {
+ // 总日志数
+ long totalCount = appOptLogService.count();
+
+ // 今日日志数
+ LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
+ QueryWrapper todayQuery = new QueryWrapper<>();
+ todayQuery.ge("opttime", todayStart);
+ long todayCount = appOptLogService.count(todayQuery);
+
+ // 本周日志数
+ LocalDateTime weekStart = LocalDateTime.now().minusDays(7);
+ QueryWrapper weekQuery = new QueryWrapper<>();
+ weekQuery.ge("logtime", weekStart);
+ long weekCount = appOptLogService.count(weekQuery);
+
+ // 本月日志数
+ LocalDateTime monthStart = LocalDateTime.now().minusDays(30);
+ QueryWrapper monthQuery = new QueryWrapper<>();
+ monthQuery.ge("logtime", monthStart);
+ long monthCount = appOptLogService.count(monthQuery);
+
+ LogStatistics statistics = new LogStatistics();
+ statistics.setTotalCount(totalCount);
+ statistics.setTodayCount(todayCount);
+ statistics.setWeekCount(weekCount);
+ statistics.setMonthCount(monthCount);
+
+ return Result.success(statistics);
+ }
+
+ @Operation(summary = "获取最近的操作日志")
+ @GetMapping("/recent")
+ @OperationLog(type = "06", module = "操作日志管理", description = "获取最近的操作日志")
+ public Result> getRecentLogs(
+ @Parameter(description = "限制条数") @RequestParam(defaultValue = "50") Integer limit) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.orderByDesc("logtime");
+ queryWrapper.last("LIMIT " + limit);
+
+ List logs = appOptLogService.list(queryWrapper);
+ return Result.success(logs);
+ }
+
+ /**
+ * 日志统计信息
+ */
+ public static class LogStatistics {
+ private Long totalCount;
+ private Long todayCount;
+ private Long weekCount;
+ private Long monthCount;
+
+ public Long getTotalCount() { return totalCount; }
+ public void setTotalCount(Long totalCount) { this.totalCount = totalCount; }
+ public Long getTodayCount() { return todayCount; }
+ public void setTodayCount(Long todayCount) { this.todayCount = todayCount; }
+ public Long getWeekCount() { return weekCount; }
+ public void setWeekCount(Long weekCount) { this.weekCount = weekCount; }
+ public Long getMonthCount() { return monthCount; }
+ public void setMonthCount(Long monthCount) { this.monthCount = monthCount; }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java b/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java
new file mode 100644
index 0000000..1feb644
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java
@@ -0,0 +1,221 @@
+package com.stdproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.stdproject.common.OperationLog;
+import com.stdproject.common.Result;
+import com.stdproject.entity.AppOrganization;
+import com.stdproject.service.IAppOrganizationService;
+import com.stdproject.service.IAppUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * 应用-组织机构 前端控制器
+ *
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@Tag(name = "组织管理", description = "组织添加、组织修改、组织删除、组织查询等功能,支持公司和部门两级树形结构")
+@RestController
+@RequestMapping("/api/org")
+public class AppOrganizationController {
+
+ @Autowired
+ private IAppOrganizationService appOrganizationService;
+ // 需要在类开头注入 IAppUserService
+ @Autowired
+ private IAppUserService appUserService;
+
+ @Operation(summary = "查询公司结构树")
+ @GetMapping("/getCompanyTree")
+ @OperationLog(type = "06", module = "组织管理", description = "查询公司结构树")
+ public Result> getCompanyTree() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("orgtype", "01");
+ queryWrapper.eq("isvaild", "1");
+ queryWrapper.orderByAsc("orgcode");
+ List children = appOrganizationService.list(queryWrapper);
+ return Result.success(children);
+ }
+
+ @Operation(summary = "查询部门列表")
+ @GetMapping("/getDepartmentList")
+ @OperationLog(type = "06", module = "组织管理", description = "查询部门列表")
+ public Result> getDepartmentList(@RequestParam String parentid,@RequestParam String keystr) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("orgtype", "02");
+ queryWrapper.eq("parentid", parentid);
+ queryWrapper.eq("isvaild", "1");
+ if (StringUtils.hasText(keystr)) {
+ queryWrapper.and(wrapper -> wrapper
+ .like("orgname", keystr)
+ .or().like("orgcode", keystr)
+ .or().like("description", keystr)
+ );
+ }
+ queryWrapper.orderByAsc("orgcode");
+ List children = appOrganizationService.list(queryWrapper);
+ return Result.success(children);
+ }
+
+
+
+ @Operation(summary = "根据ID查询组织详情")
+ @GetMapping("getDetailById/{id}")
+ @OperationLog(type = "06", module = "组织管理", description = "根据ID查询组织详情")
+ public Result getDetailById(@PathVariable String id) {
+ AppOrganization organization = appOrganizationService.getById(id);
+ return Result.success(organization);
+ }
+
+
+ @Operation(summary = "新增组织")
+ @PostMapping("add")
+ @OperationLog(type = "01", module = "组织管理", description = "新增组织")
+ public Result add(@RequestBody @Valid AppOrganization appOrganization) {
+ // 设置默认值
+ //生成新的组织编号 公司编号:010101 部门编号:0001
+ appOrganization.setOrgcode(getNewOrgCode(appOrganization.getOrgtype(), appOrganization.getParentid()));
+ //设置状态为有效
+ appOrganization.setIsvaild("1");
+ // 从当前登录用户上下文中获取用户名
+ appOrganization.setLastmodifier(appUserService.getCurrentUsername());
+ appOrganization.setLastmodifydate(LocalDateTime.now());
+ boolean success = appOrganizationService.save(appOrganization);
+ return success ? Result.success("新增成功") : Result.error("新增失败");
+ }
+
+ @Operation(summary = "修改组织")
+ @PostMapping("update")
+ @OperationLog(type = "02", module = "组织管理", description = "修改组织")
+ public Result update(@RequestBody @Valid AppOrganization appOrganization) {
+ // 检查组织是否存在
+ AppOrganization existOrg = appOrganizationService.getById(appOrganization.getId());
+ if (existOrg == null) {
+ return Result.error("组织不存在");
+ }
+ // 验证父级组织变更的合法性
+ if (StringUtils.hasText(appOrganization.getParentid()) && !"0".equals(appOrganization.getParentid())) {
+ // 不能将自己设为父级
+ if (appOrganization.getId().equals(appOrganization.getParentid())) {
+ return Result.error("不能将自己设为父级组织");
+ }
+ // 验证父级组织是否存在
+ AppOrganization parent = appOrganizationService.getById(appOrganization.getParentid());
+ if (parent == null || !"1".equals(parent.getIsvaild())) {
+ return Result.error("父级组织不存在或已禁用");
+ }
+
+ if ("02".equals(parent.getOrgtype())) {
+ return Result.error("部门下不能有子组织");
+ }
+ }
+ appOrganization.setLastmodifier(appUserService.getCurrentUsername());
+ appOrganization.setLastmodifydate(LocalDateTime.now());
+ boolean success = appOrganizationService.updateById(appOrganization);
+ return success ? Result.success("修改成功") : Result.error("修改失败");
+ }
+
+ @Operation(summary = "删除组织")
+ @PostMapping("delete/{id}")
+ @OperationLog(type = "03", module = "组织管理", description = "删除组织")
+ public Result delete(@PathVariable String id) {
+ boolean success = appOrganizationService.removeById(id);
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ /**
+ * 根据组织类型和父节点生成新的组织编码
+ *
+ * @param orgType 组织类型:"01"表示公司,"02"表示部门
+ * @param parentId 父节点ID,新增部门时使用
+ * @return 新的组织编码
+ */
+ private String getNewOrgCode(String orgType, String parentId) {
+ if ("01".equals(orgType)) {
+ // 公司类型:查找当前最大的公司编码并递增最后一级
+ AppOrganization parentOrg = null;
+ if (StringUtils.hasText(parentId) && !"0".equals(parentId)) {
+ parentOrg = appOrganizationService.getById(parentId);
+ if (parentOrg == null || !"1".equals(parentOrg.getIsvaild())) {
+ throw new IllegalArgumentException("无效的父级组织");
+ }
+ }
+
+ // 获取父节点编码(顶级为“01”)
+ String parentCode = parentOrg != null ? parentOrg.getOrgcode() : "01";
+
+ // 获取当前父节点下的最大子节点编码
+ String maxCompanyCode = getMaxCodeByParentId("01",parentId);
+
+ int nextLevel = 1;
+ if (maxCompanyCode != null && !maxCompanyCode.isEmpty()) {
+ try {
+ // 提取最后两位
+ String lastTwo = maxCompanyCode.substring(maxCompanyCode.length() - 2);
+ nextLevel = Integer.parseInt(lastTwo) + 1;
+ } catch (Exception e) {
+ nextLevel = 1; // 出错则重置为1
+ }
+ }
+
+ if (nextLevel > 99) {
+ throw new RuntimeException("公司编码已达上限,请联系管理员处理");
+ }
+
+ return parentCode + String.format("%02d", nextLevel);
+ } else if ("02".equals(orgType) && StringUtils.hasText(parentId)) {
+ // 部门类型:查找该公司下最大部门编码并递增
+ AppOrganization parentOrg = appOrganizationService.getById(parentId);
+ if (parentOrg == null || !"1".equals(parentOrg.getIsvaild())) {
+ throw new IllegalArgumentException("无效的父级组织");
+ }
+
+ // 获取该父节点下最大部门编号
+ String maxDeptCodeStr = getMaxCodeByParentId("02",parentId);
+ int nextSeq = 1;
+
+ if (maxDeptCodeStr != null && !maxDeptCodeStr.isEmpty()) {
+ try {
+ nextSeq = Integer.parseInt(maxDeptCodeStr) + 1;
+ } catch (NumberFormatException e) {
+ nextSeq = 1;
+ }
+ }
+
+ if (nextSeq > 9999) {
+ throw new RuntimeException("部门编码已达上限,请联系管理员处理");
+ }
+
+ return String.format("%04d", nextSeq);
+ } else {
+ throw new IllegalArgumentException("无法生成组织编码:参数不完整");
+ }
+ }
+
+
+ private String getMaxCodeByParentId(String orgType,String parentId) {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper()
+ .eq(AppOrganization::getOrgtype, orgType)
+ .eq(AppOrganization::getParentid, parentId)
+ .eq(AppOrganization::getIsvaild, "1")
+ .orderByDesc(AppOrganization::getOrgcode)
+ .select(AppOrganization::getOrgcode)
+ .last("LIMIT 1");
+ // 使用selectList方法获取符合条件的记录列表
+ List list = appOrganizationService.list(queryWrapper);
+ // 如果列表不为空,返回第一个记录的orgcode
+ return list.isEmpty() ? null : list.get(0).getOrgcode();
+ }
+
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/controller/AppRoleController.java b/backend/src/main/java/com/stdproject/controller/AppRoleController.java
new file mode 100644
index 0000000..6f660a9
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/controller/AppRoleController.java
@@ -0,0 +1,320 @@
+package com.stdproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.stdproject.common.OperationLog;
+import com.stdproject.common.PageRequest;
+import com.stdproject.common.Result;
+import com.stdproject.entity.AppRole;
+import com.stdproject.entity.AppRoleMenu;
+import com.stdproject.entity.AppRoleUser;
+import com.stdproject.service.IAppRoleMenuService;
+import com.stdproject.service.IAppRoleService;
+import com.stdproject.service.IAppRoleUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * 应用-角色表 前端控制器
+ *
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@Tag(name = "角色管理", description = "角色添加、角色修改、角色删除、角色查询,角色权限分配等功能")
+@RestController
+@RequestMapping("/api/role")
+public class AppRoleController {
+
+ @Autowired
+ private IAppRoleService appRoleService;
+
+ @Autowired
+ private IAppRoleMenuService appRoleMenuService;
+
+ @Autowired
+ private IAppRoleUserService appRoleUserService;
+
+ @Operation(summary = "分页查询角色列表")
+ @PostMapping("/page")
+ @OperationLog(type = "06", module = "角色管理", description = "分页查询角色列表")
+ public Result> page(@RequestBody @Valid PageRequest pageRequest) {
+ Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+
+ // 关键字搜索
+ if (StringUtils.hasText(pageRequest.getKeyword())) {
+ queryWrapper.and(wrapper -> wrapper
+ .like("rolename", pageRequest.getKeyword())
+ .or().like("rolecode", pageRequest.getKeyword())
+ .or().like("description", pageRequest.getKeyword())
+ );
+ }
+
+ // 只查询有效的角色
+ queryWrapper.eq("isvaild", "1");
+
+ // 排序
+ if (StringUtils.hasText(pageRequest.getOrderBy())) {
+ if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
+ queryWrapper.orderByAsc(pageRequest.getOrderBy());
+ } else {
+ queryWrapper.orderByDesc(pageRequest.getOrderBy());
+ }
+ } else {
+ queryWrapper.orderByAsc("rolecode");
+ }
+
+ IPage result = appRoleService.page(page, queryWrapper);
+ return Result.success(result);
+ }
+
+ @Operation(summary = "查询所有有效角色列表")
+ @GetMapping("/list")
+ @OperationLog(type = "06", module = "角色管理", description = "查询所有有效角色列表")
+ public Result> list() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("isvaild", "1");
+ queryWrapper.orderByAsc("rolecode");
+
+ List roles = appRoleService.list(queryWrapper);
+ return Result.success(roles);
+ }
+
+ @Operation(summary = "根据ID查询角色详情")
+ @GetMapping("/{id}")
+ @OperationLog(type = "06", module = "角色管理", description = "查询角色详情")
+ public Result getById(@Parameter(description = "角色ID") @PathVariable String id) {
+ AppRole role = appRoleService.getById(id);
+ return Result.success(role);
+ }
+ @Operation(summary = "新增角色")
+ @PostMapping
+ @OperationLog(type = "01", module = "角色管理", description = "新增角色")
+ public Result save(@RequestBody @Valid AppRole appRole) {
+ // 检查角色名称是否已存在
+ QueryWrapper nameQuery = new QueryWrapper<>();
+ nameQuery.eq("rolename", appRole.getRolename());
+ nameQuery.eq("isvaild", "1");
+ AppRole existRoleName = appRoleService.getOne(nameQuery);
+ if (ObjectUtils.isNotEmpty(existRoleName)) {
+ return Result.error("角色名称已存在");
+ }
+ // 自动生成角色编号
+ String maxRoleCode = getMaxRoleCode();
+ String newRoleCode;
+ if (maxRoleCode == null) {
+ newRoleCode = "001";
+ } else {
+ int nextCode = Integer.parseInt(maxRoleCode) + 1;
+ newRoleCode = String.format("%03d", nextCode);
+ }
+ appRole.setRolecode(newRoleCode);
+ // 设置默认值
+ appRole.setIsvaild("1");
+ appRole.setLastmodifier("admin");
+ appRole.setLastmodifydate(LocalDateTime.now());
+ boolean success = appRoleService.save(appRole);
+ return success ? Result.success("新增成功") : Result.error("新增失败");
+ }
+
+ /**
+ * 获取最大的角色编号
+ * @return 最大角色编号
+ */
+ private String getMaxRoleCode() {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper()
+ .eq(AppRole::getIsvaild, "1")
+ .orderByDesc(AppRole::getRolecode)
+ .select(AppRole::getRolecode)
+ .last("LIMIT 1");
+ List list = appRoleService.list(queryWrapper);
+ return list.isEmpty() ? null : list.get(0).getRolecode();
+ }
+
+ @Operation(summary = "修改角色")
+ @PutMapping
+ @OperationLog(type = "02", module = "角色管理", description = "修改角色")
+ public Result update(@RequestBody @Valid AppRole appRole) {
+ // 检查角色是否存在
+ AppRole existRole = appRoleService.getById(appRole.getId());
+ if (existRole == null) {
+ return Result.error("角色不存在");
+ }
+
+ // 如果修改了角色名称,检查新名称是否已被其他角色使用
+ if (!existRole.getRolename().equals(appRole.getRolename())) {
+ QueryWrapper nameQuery = new QueryWrapper<>();
+ nameQuery.eq("rolename", appRole.getRolename());
+ nameQuery.eq("isvaild", "1");
+ nameQuery.ne("id", appRole.getId());
+ AppRole roleWithSameName = appRoleService.getOne(nameQuery);
+ if (roleWithSameName != null) {
+ return Result.error("角色名称已被其他角色使用");
+ }
+ }
+ appRole.setLastmodifier("admin");
+ appRole.setLastmodifydate(LocalDateTime.now());
+ boolean success = appRoleService.updateById(appRole);
+ return success ? Result.success("修改成功") : Result.error("修改失败");
+ }
+
+ @Operation(summary = "删除角色")
+ @DeleteMapping("/{id}")
+ @OperationLog(type = "03", module = "角色管理", description = "删除角色")
+ public Result delete(@Parameter(description = "角色ID") @PathVariable String id) {
+ // 检查是否有用户关联此角色
+ QueryWrapper userQuery = new QueryWrapper<>();
+ userQuery.eq("roleid", id);
+ long userCount = appRoleUserService.count(userQuery);
+ if (userCount > 0) {
+ return Result.error("存在用户关联此角色,无法删除");
+ }
+
+ // 软删除:设置为无效
+ AppRole role = appRoleService.getById(id);
+ if (role != null) {
+ role.setIsvaild("0");
+ role.setLastmodifydate(LocalDateTime.now());
+ boolean success = appRoleService.updateById(role);
+
+ if (success) {
+ // 同时删除角色菜单关联
+ QueryWrapper menuQuery = new QueryWrapper<>();
+ menuQuery.eq("roleid", id);
+ appRoleMenuService.remove(menuQuery);
+ }
+
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ return Result.error("角色不存在");
+ }
+
+
+
+ @Operation(summary = "根据角色类型查询角色列表")
+ @GetMapping("/type/{type}")
+ @OperationLog(type = "06", module = "角色管理", description = "根据角色类型查询角色列表")
+ public Result> getByType(@Parameter(description = "角色类型:1-应用管理员,2-应用普通用户") @PathVariable String type) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("type", type);
+ queryWrapper.eq("isvaild", "1");
+ queryWrapper.orderByAsc("rolecode");
+ List roles = appRoleService.list(queryWrapper);
+ return Result.success(roles);
+ }
+
+ @Operation(summary = "分配角色菜单权限")
+ @PostMapping("/assign-menus/{roleId}")
+ @OperationLog(type = "02", module = "角色管理", description = "分配角色菜单权限")
+ public Result assignMenus(
+ @Parameter(description = "角色ID") @PathVariable String roleId,
+ @RequestBody List menuIds) {
+
+ // 检查角色是否存在
+ AppRole role = appRoleService.getById(roleId);
+ if (role == null || !"1".equals(role.getIsvaild())) {
+ return Result.error("角色不存在或已禁用");
+ }
+
+ // 先删除原有的菜单权限
+ QueryWrapper deleteQuery = new QueryWrapper<>();
+ deleteQuery.eq("roleid", roleId);
+ appRoleMenuService.remove(deleteQuery);
+
+ // 添加新的菜单权限
+ if (menuIds != null && !menuIds.isEmpty()) {
+ List roleMenus = menuIds.stream().map(menuId -> {
+ AppRoleMenu roleMenu = new AppRoleMenu();
+ roleMenu.setRoleid(roleId);
+ roleMenu.setMenuid(menuId);
+ roleMenu.setAppId(role.getAppId());
+ return roleMenu;
+ }).collect(Collectors.toList());
+
+ boolean success = appRoleMenuService.saveBatch(roleMenus);
+ return success ? Result.success("权限分配成功") : Result.error("权限分配失败");
+ }
+
+ return Result.success("权限分配成功");
+ }
+
+ @Operation(summary = "获取角色的菜单权限")
+ @GetMapping("/menus/{roleId}")
+ @OperationLog(type = "06", module = "角色管理", description = "获取角色的菜单权限")
+ public Result> getRoleMenus(@Parameter(description = "角色ID") @PathVariable String roleId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("roleid", roleId);
+
+ List roleMenus = appRoleMenuService.list(queryWrapper);
+ List menuIds = roleMenus.stream()
+ .map(AppRoleMenu::getMenuid)
+ .collect(Collectors.toList());
+
+ return Result.success(menuIds);
+ }
+
+ @Operation(summary = "分配用户角色")
+ @PostMapping("/assign-users/{roleId}")
+ @OperationLog(type = "02", module = "角色管理", description = "分配用户角色")
+ public Result assignUsers(
+ @Parameter(description = "角色ID") @PathVariable String roleId,
+ @RequestBody List userIds) {
+
+ // 检查角色是否存在
+ AppRole role = appRoleService.getById(roleId);
+ if (role == null || !"1".equals(role.getIsvaild())) {
+ return Result.error("角色不存在或已禁用");
+ }
+
+ // 先删除原有的用户角色关联
+ QueryWrapper deleteQuery = new QueryWrapper<>();
+ deleteQuery.eq("roleid", roleId);
+ appRoleUserService.remove(deleteQuery);
+
+ // 添加新的用户角色关联
+ if (userIds != null && !userIds.isEmpty()) {
+ List roleUsers = userIds.stream().map(userId -> {
+ AppRoleUser roleUser = new AppRoleUser();
+ roleUser.setRoleid(roleId);
+ roleUser.setUserid(userId);
+ roleUser.setAppId(role.getAppId());
+ return roleUser;
+ }).collect(Collectors.toList());
+
+ boolean success = appRoleUserService.saveBatch(roleUsers);
+ return success ? Result.success("用户分配成功") : Result.error("用户分配失败");
+ }
+
+ return Result.success("用户分配成功");
+ }
+
+ @Operation(summary = "获取角色的用户列表")
+ @GetMapping("/users/{roleId}")
+ @OperationLog(type = "06", module = "角色管理", description = "获取角色的用户列表")
+ public Result> getRoleUsers(@Parameter(description = "角色ID") @PathVariable String roleId) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("roleid", roleId);
+
+ List roleUsers = appRoleUserService.list(queryWrapper);
+ List userIds = roleUsers.stream()
+ .map(AppRoleUser::getUserid)
+ .collect(Collectors.toList());
+
+ return Result.success(userIds);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/stdproject/controller/AppUserController.java b/backend/src/main/java/com/stdproject/controller/AppUserController.java
new file mode 100644
index 0000000..15ceb55
--- /dev/null
+++ b/backend/src/main/java/com/stdproject/controller/AppUserController.java
@@ -0,0 +1,277 @@
+package com.stdproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.stdproject.common.OperationLog;
+import com.stdproject.common.PageRequest;
+import com.stdproject.common.Result;
+import com.stdproject.entity.AppUser;
+import com.stdproject.service.IAppUserService;
+import com.stdproject.utils.PasswordUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * 应用-用户表 前端控制器
+ *
+ *
+ * @author StdProject
+ * @since 2023-12-07
+ */
+@Tag(name = "用户管理", description = "用户信息维护、用户角色分配、用户登录、用户修改密码、用户修改个人信息等功能")
+@RestController
+@RequestMapping("/app-user")
+public class AppUserController {
+
+ @Autowired
+ private IAppUserService appUserService;
+
+ @Operation(summary = "分页查询用户列表")
+ @PostMapping("/page")
+ @OperationLog(type = "06", module = "用户管理", description = "分页查询用户列表")
+ public Result> page(@RequestBody @Valid PageRequest pageRequest) {
+ Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+
+ // 关键字搜索
+ if (StringUtils.hasText(pageRequest.getKeyword())) {
+ queryWrapper.and(wrapper -> wrapper
+ .like("username", pageRequest.getKeyword())
+ .or().like("nickname", pageRequest.getKeyword())
+ .or().like("email", pageRequest.getKeyword())
+ .or().like("phone", pageRequest.getKeyword())
+ );
+ }
+
+ // 排序
+ if (StringUtils.hasText(pageRequest.getOrderBy())) {
+ if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
+ queryWrapper.orderByAsc(pageRequest.getOrderBy());
+ } else {
+ queryWrapper.orderByDesc(pageRequest.getOrderBy());
+ }
+ } else {
+ queryWrapper.orderByDesc("lastmodifydate");
+ }
+
+ IPage result = appUserService.page(page, queryWrapper);
+ return Result.success(result);
+ }
+
+ @Operation(summary = "根据ID查询用户详情")
+ @GetMapping("/{id}")
+ @OperationLog(type = "06", module = "用户管理", description = "查询用户详情")
+ public Result getById(@Parameter(description = "用户ID") @PathVariable String id) {
+ AppUser user = appUserService.getById(id);
+ if (user != null) {
+ // 不返回密码信息
+ user.setPassword(null);
+ }
+ return Result.success(user);
+ }
+
+ @Operation(summary = "根据用户名查询用户")
+ @GetMapping("/username/{username}")
+ @OperationLog(type = "06", module = "用户管理", description = "根据用户名查询用户")
+ public Result getByUsername(@Parameter(description = "用户名") @PathVariable String username) {
+ AppUser user = appUserService.findByUsername(username);
+ if (user != null) {
+ // 不返回密码信息
+ user.setPassword(null);
+ }
+ return Result.success(user);
+ }
+
+ @Operation(summary = "新增用户")
+ @PostMapping
+ @OperationLog(type = "01", module = "用户管理", description = "新增用户")
+ public Result save(@RequestBody @Valid AppUser appUser) {
+ // 检查用户名是否已存在
+ AppUser existUser = appUserService.findByUsername(appUser.getUsername());
+ if (existUser != null) {
+ return Result.error("用户名已存在");
+ }
+
+ // 设置默认值
+ if (!StringUtils.hasText(appUser.getStatus())) {
+ appUser.setStatus("1"); // 默认有效
+ }
+ if (appUser.getPwdvalidperiod() == null) {
+ appUser.setPwdvalidperiod(90); // 默认90天
+ }
+ if (appUser.getFailednum() == null) {
+ appUser.setFailednum(0);
+ }
+
+ // 密码加密
+ if (StringUtils.hasText(appUser.getPassword())) {
+ appUser.setPassword(PasswordUtils.encodePassword(appUser.getPassword()));
+ }
+
+ appUser.setLastmodifydate(LocalDateTime.now());
+ boolean success = appUserService.save(appUser);
+ return success ? Result.success("新增成功") : Result.error("新增失败");
+ }
+
+ @Operation(summary = "修改用户")
+ @PutMapping
+ @OperationLog(type = "02", module = "用户管理", description = "修改用户")
+ public Result update(@RequestBody @Valid AppUser appUser) {
+ // 检查用户是否存在
+ AppUser existUser = appUserService.getById(appUser.getId());
+ if (existUser == null) {
+ return Result.error("用户不存在");
+ }
+
+ // 如果修改了用户名,检查新用户名是否已被其他用户使用
+ if (!existUser.getUsername().equals(appUser.getUsername())) {
+ AppUser userWithSameName = appUserService.findByUsername(appUser.getUsername());
+ if (userWithSameName != null && !userWithSameName.getId().equals(appUser.getId())) {
+ return Result.error("用户名已被其他用户使用");
+ }
+ }
+
+ // 如果传入了新密码,进行加密
+ if (StringUtils.hasText(appUser.getPassword())) {
+ appUser.setPassword(PasswordUtils.encodePassword(appUser.getPassword()));
+ appUser.setPwdresettime(LocalDateTime.now());
+ } else {
+ // 如果没有传入密码,保持原密码不变
+ appUser.setPassword(existUser.getPassword());
+ }
+
+ appUser.setLastmodifydate(LocalDateTime.now());
+ boolean success = appUserService.updateById(appUser);
+ return success ? Result.success("修改成功") : Result.error("修改失败");
+ }
+
+ @Operation(summary = "删除用户")
+ @DeleteMapping("/{id}")
+ @OperationLog(type = "03", module = "用户管理", description = "删除用户")
+ public Result delete(@Parameter(description = "用户ID") @PathVariable String id) {
+ boolean success = appUserService.removeById(id);
+ return success ? Result.success("删除成功") : Result.error("删除失败");
+ }
+
+ @Operation(summary = "批量删除用户")
+ @DeleteMapping("/batch")
+ @OperationLog(type = "03", module = "用户管理", description = "批量删除用户")
+ public Result deleteBatch(@RequestBody List ids) {
+ boolean success = appUserService.removeByIds(ids);
+ return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
+ }
+
+ @Operation(summary = "修改用户密码")
+ @PutMapping("/password/{id}")
+ @OperationLog(type = "02", module = "用户管理", description = "修改用户密码")
+ public Result updatePassword(
+ @Parameter(description = "用户ID") @PathVariable String id,
+ @RequestBody PasswordUpdateRequest request) {
+ AppUser user = appUserService.getById(id);
+ if (user == null) {
+ return Result.error("用户不存在");
+ }
+
+ // 验证旧密码
+ if (!PasswordUtils.matches(request.getOldPassword(), user.getPassword())) {
+ return Result.error("原密码错误");
+ }
+
+ // 更新新密码
+ user.setPassword(PasswordUtils.encodePassword(request.getNewPassword()));
+ user.setPwdresettime(LocalDateTime.now());
+ user.setLastmodifydate(LocalDateTime.now());
+
+ boolean success = appUserService.updateById(user);
+ return success ? Result.success("密码修改成功") : Result.error("密码修改失败");
+ }
+
+ @Operation(summary = "重置用户密码")
+ @PutMapping("/reset-password/{id}")
+ @OperationLog(type = "02", module = "用户管理", description = "重置用户密码")
+ public Result resetPassword(
+ @Parameter(description = "用户ID") @PathVariable String id,
+ @RequestBody PasswordResetRequest request) {
+ AppUser user = appUserService.getById(id);
+ if (user == null) {
+ return Result.error("用户不存在");
+ }
+
+ // 重置密码
+ user.setPassword(PasswordUtils.encodePassword(request.getNewPassword()));
+ user.setPwdresettime(LocalDateTime.now());
+ user.setLastmodifydate(LocalDateTime.now());
+
+ boolean success = appUserService.updateById(user);
+ return success ? Result.success("密码重置成功") : Result.error("密码重置失败");
+ }
+
+ @Operation(summary = "启用/禁用用户")
+ @PutMapping("/status/{id}")
+ @OperationLog(type = "02", module = "用户管理", description = "启用/禁用用户")
+ public Result updateStatus(
+ @Parameter(description = "用户ID") @PathVariable String id,
+ @RequestParam String status) {
+ AppUser user = appUserService.getById(id);
+ if (user == null) {
+ return Result.error("用户不存在");
+ }
+
+ user.setStatus(status);
+ user.setLastmodifydate(LocalDateTime.now());
+
+ boolean success = appUserService.updateById(user);
+ String message = "1".equals(status) ? "启用成功" : "禁用成功";
+ return success ? Result.success(message) : Result.error("操作失败");
+ }
+
+ @Operation(summary = "根据组织ID查询用户列表")
+ @GetMapping("/org/{orgId}")
+ @OperationLog(type = "06", module = "用户管理", description = "根据组织ID查询用户列表")
+ public Result> getByOrgId(@Parameter(description = "组织ID") @PathVariable String orgId) {
+ QueryWrapper