From 74acfd664f62f34b35cbb2cc90d9d2376b490900 Mon Sep 17 00:00:00 2001 From: gin Date: Wed, 17 Jun 2026 17:40:12 +0800 Subject: [PATCH] refactor: migrate backend to TypeScript --- .dockerignore | 24 + .env.example | 10 +- .eslintignore | 13 + .gitignore | 2 + frontend/.npmrc => .npmrc | 1 - frontend/.eslintignore => .prettierignore | 6 +- frontend/.prettierrc.cjs => .prettierrc.cjs | 0 .stylelintignore | 17 + AGENTS.md | 9 +- README.md | 204 +- backend/.dockerignore | 12 - backend/.env.example | 10 + backend/.gitignore | 2 + backend/.mvn/wrapper/maven-wrapper.jar | Bin 62547 -> 0 bytes backend/.mvn/wrapper/maven-wrapper.properties | 18 - backend/Dockerfile | 19 - backend/GoogleStyle.xml | 569 --- backend/LICENSE | 21 - backend/README.md | 378 +- backend/agileboot-admin/pom.xml | 72 - .../admin/AgileBootAdminApplication.java | 32 - .../controller/app/AppAuthController.java | 84 - .../app/AppCollaborationRecordController.java | 90 - .../controller/app/AppProfileController.java | 56 - .../CollaborationRecordController.java | 105 - .../controller/common/FileController.java | 130 - .../controller/common/LoginController.java | 152 - .../controller/system/MonitorController.java | 82 - .../system/SysConfigController.java | 88 - .../controller/system/SysLogsController.java | 120 - .../controller/system/SysMenuController.java | 120 - .../system/SysNoticeController.java | 122 - .../system/SysProfileController.java | 98 - .../controller/system/SysRoleController.java | 181 - .../controller/system/SysUserController.java | 169 - .../customize/aop/accessLog/AccessLog.java | 45 - .../aop/accessLog/AccessLogAspect.java | 59 - .../aop/accessLog/OperationLogModel.java | 160 - .../customize/async/AsyncTaskFactory.java | 78 - .../config/JwtAuthenticationTokenFilter.java | 53 - .../customize/config/SecurityConfig.java | 165 - .../customize/service/login/LoginService.java | 256 -- .../customize/service/login/TokenService.java | 163 - .../service/login/UserDetailsServiceImpl.java | 108 - .../service/login/command/LoginCommand.java | 33 - .../service/login/dto/CaptchaDTO.java | 15 - .../service/login/dto/ConfigDTO.java | 20 - .../DataPermissionCheckerFactory.java | 51 - .../permission/DataPermissionService.java | 64 - .../permission/MenuPermissionService.java | 48 - .../model/AbstractDataPermissionChecker.java | 22 - .../permission/model/DataCondition.java | 20 - .../checker/AllDataPermissionChecker.java | 21 - .../checker/DefaultDataPermissionChecker.java | 22 - .../OnlySelfDataPermissionChecker.java | 35 - .../src/main/resources/application-dev.yml | 105 - .../src/main/resources/application-prod.yml | 65 - .../src/main/resources/application-test.yml | 53 - .../src/main/resources/application.yml | 46 - .../admin/config/AgileBootConfigTest.java | 41 - .../OnlySelfDataPermissionCheckerTest.java | 55 - backend/agileboot-api/pom.xml | 47 - .../agileboot/api/AgileBooApiApplication.java | 32 - .../api/controller/OrderController.java | 25 - .../api/controller/app/AppController.java | 39 - .../controller/common/LoginController.java | 38 - .../config/JwtAuthenticationFilter.java | 52 - .../api/customize/config/SecurityConfig.java | 85 - .../customize/service/JwtTokenService.java | 127 - .../api/customize/util/ApiEncryptor.java | 12 - .../src/main/resources/application.yml | 28 - backend/agileboot-common/pom.xml | 189 - .../common/annotation/ExcelColumn.java | 19 - .../common/annotation/ExcelSheet.java | 20 - .../common/config/AgileBootConfig.java | 109 - .../agileboot/common/constant/Constants.java | 86 - .../common/core/base/BaseController.java | 40 - .../common/core/base/BaseEntity.java | 46 - .../common/core/dto/ResponseDTO.java | 59 - .../common/core/page/AbstractPageQuery.java | 44 - .../common/core/page/AbstractQuery.java | 90 - .../agileboot/common/core/page/PageDTO.java | 38 - .../com/agileboot/common/enums/BasicEnum.java | 24 - .../agileboot/common/enums/BasicEnumUtil.java | 63 - .../common/enums/DictionaryEnum.java | 15 - .../common/enums/common/BusinessTypeEnum.java | 54 - .../common/enums/common/ConfigKeyEnum.java | 34 - .../common/enums/common/GenderEnum.java | 47 - .../common/enums/common/LoginStatusEnum.java | 46 - .../enums/common/MenuComponentEnum.java | 36 - .../common/enums/common/MenuTypeEnum.java | 38 - .../common/enums/common/NoticeStatusEnum.java | 45 - .../common/enums/common/NoticeTypeEnum.java | 47 - .../enums/common/OperationStatusEnum.java | 45 - .../common/enums/common/OperatorTypeEnum.java | 39 - .../enums/common/RequestMethodEnum.java | 39 - .../common/enums/common/StatusEnum.java | 44 - .../common/enums/common/UserStatusEnum.java | 49 - .../enums/common/VisibleStatusEnum.java | 46 - .../common/enums/common/YesOrNoEnum.java | 46 - .../common/enums/dictionary/CssTag.java | 17 - .../common/enums/dictionary/Dictionary.java | 25 - .../enums/dictionary/DictionaryData.java | 26 - .../common/exception/ApiException.java | 75 - .../common/exception/error/ErrorCode.java | 390 -- .../exception/error/ErrorCodeInterface.java | 26 - .../common/utils/ServletHolderUtil.java | 77 - .../common/utils/file/FileUploadUtils.java | 224 - .../common/utils/i18n/MessageUtils.java | 28 - .../agileboot/common/utils/ip/IpRegion.java | 35 - .../common/utils/ip/IpRegionUtil.java | 41 - .../com/agileboot/common/utils/ip/IpUtil.java | 53 - .../common/utils/ip/OfflineIpRegionUtil.java | 64 - .../common/utils/ip/OnlineIpRegionUtil.java | 52 - .../utils/jackson/JacksonException.java | 12 - .../common/utils/jackson/JacksonUtil.java | 684 --- .../common/utils/poi/CustomExcelUtil.java | 101 - .../common/utils/poi/TrimXssEditor.java | 20 - .../common/utils/time/DatePickUtil.java | 55 - .../src/main/resources/ip2region.xdb | Bin 11065998 -> 0 bytes .../core/exception/ApiExceptionTest.java | 29 - .../common/enums/BasicEnumUtilTest.java | 20 - .../error/ErrorCodeInterfaceTest.java | 14 - .../common/query/AbstractQueryTest.java | 80 - .../utils/file/FileUploadUtilsTest.java | 119 - .../common/utils/ip/IpRegionUtilTest.java | 65 - .../agileboot/common/utils/ip/IpUtilTest.java | 41 - .../utils/ip/OfflineIpRegionUtilTest.java | 52 - .../utils/ip/OnlineIpRegionUtilTest.java | 63 - .../common/utils/jackson/JacksonUtilTest.java | 61 - .../common/utils/jackson/Person.java | 42 - .../common/utils/poi/CustomExcelUtilTest.java | 40 - .../agileboot/common/utils/poi/PostDTO.java | 37 - .../common/utils/time/DatePickUtilTest.java | 46 - backend/agileboot-domain/pom.xml | 41 - ...CollaborationRecordApplicationService.java | 588 --- .../AddCollaborationRecordCommand.java | 83 - .../CollaborationExpenditureCommand.java | 25 - .../command/CollaborationFileCommand.java | 30 - .../CollaborationSettlementCommand.java | 28 - .../command/CollaborationTaskCommand.java | 16 - .../UpdateCollaborationRecordCommand.java | 19 - .../db/CollaborationExpenditureEntity.java | 46 - .../db/CollaborationExpenditureMapper.java | 10 - .../db/CollaborationExpenditureService.java | 16 - .../CollaborationExpenditureServiceImpl.java | 36 - .../record/db/CollaborationFileEntity.java | 51 - .../record/db/CollaborationFileMapper.java | 10 - .../record/db/CollaborationFileService.java | 16 - .../db/CollaborationFileServiceImpl.java | 36 - .../record/db/CollaborationRecordEntity.java | 100 - .../record/db/CollaborationRecordMapper.java | 10 - .../record/db/CollaborationRecordService.java | 10 - .../db/CollaborationRecordServiceImpl.java | 14 - .../db/CollaborationSettlementEntity.java | 49 - .../db/CollaborationSettlementMapper.java | 10 - .../db/CollaborationSettlementService.java | 16 - .../CollaborationSettlementServiceImpl.java | 36 - .../record/db/CollaborationTaskEntity.java | 42 - .../record/db/CollaborationTaskMapper.java | 10 - .../record/db/CollaborationTaskService.java | 16 - .../db/CollaborationTaskServiceImpl.java | 36 - .../dto/CollaborationExpenditureDTO.java | 33 - .../record/dto/CollaborationFileDTO.java | 35 - .../CollaborationMonthlyStatisticsDTO.java | 26 - .../record/dto/CollaborationOptionDTO.java | 22 - .../record/dto/CollaborationRecordDTO.java | 75 - .../dto/CollaborationRecordDetailDTO.java | 30 - .../dto/CollaborationSettlementDTO.java | 35 - .../record/dto/CollaborationTaskDTO.java | 30 - .../record/dto/SettlementStatusDTO.java | 19 - .../enumtype/CollaborationFileTypeEnum.java | 12 - .../enumtype/CollaborationOptionEnum.java | 45 - .../record/enumtype/SettlementStatusEnum.java | 57 - .../model/CollaborationRecordModel.java | 79 - .../CollaborationRecordModelFactory.java | 31 - .../query/CollaborationRecordQuery.java | 56 - .../domain/common/cache/CacheCenter.java | 44 - .../common/cache/GuavaCacheService.java | 27 - .../domain/common/cache/MapCache.java | 83 - .../common/cache/RedisCacheService.java | 68 - .../common/command/BulkOperationCommand.java | 28 - .../common/dto/CurrentLoginUserDTO.java | 18 - .../agileboot/domain/common/dto/TokenDTO.java | 17 - .../domain/common/dto/TreeSelectedDTO.java | 18 - .../domain/common/dto/UploadDTO.java | 18 - .../domain/common/dto/UploadFileDTO.java | 19 - .../config/ConfigApplicationService.java | 52 - .../config/command/ConfigUpdateCommand.java | 24 - .../system/config/db/SysConfigEntity.java | 64 - .../system/config/db/SysConfigMapper.java | 15 - .../system/config/db/SysConfigService.java | 23 - .../config/db/SysConfigServiceImpl.java | 35 - .../domain/system/config/dto/ConfigDTO.java | 47 - .../system/config/model/ConfigModel.java | 60 - .../config/model/ConfigModelFactory.java | 33 - .../system/config/query/ConfigQuery.java | 42 - .../system/log/LogApplicationService.java | 54 - .../system/log/db/SysLoginInfoEntity.java | 79 - .../system/log/db/SysLoginInfoMapper.java | 15 - .../system/log/db/SysLoginInfoService.java | 15 - .../log/db/SysLoginInfoServiceImpl.java | 18 - .../system/log/db/SysOperationLogEntity.java | 107 - .../system/log/db/SysOperationLogMapper.java | 15 - .../system/log/db/SysOperationLogService.java | 15 - .../log/db/SysOperationLogServiceImpl.java | 18 - .../domain/system/log/dto/LoginLogDTO.java | 63 - .../system/log/dto/OperationLogDTO.java | 89 - .../system/log/query/LoginLogQuery.java | 37 - .../system/log/query/OperationLogQuery.java | 34 - .../system/menu/MenuApplicationService.java | 171 - .../system/menu/command/AddMenuCommand.java | 37 - .../menu/command/UpdateMenuCommand.java | 17 - .../domain/system/menu/db/SysMenuEntity.java | 80 - .../domain/system/menu/db/SysMenuMapper.java | 50 - .../domain/system/menu/db/SysMenuService.java | 59 - .../system/menu/db/SysMenuServiceImpl.java | 75 - .../domain/system/menu/dto/ExtraIconDTO.java | 16 - .../domain/system/menu/dto/MenuDTO.java | 76 - .../domain/system/menu/dto/MenuDetailDTO.java | 30 - .../domain/system/menu/dto/MetaDTO.java | 60 - .../domain/system/menu/dto/RouterDTO.java | 77 - .../domain/system/menu/dto/TransitionDTO.java | 19 - .../domain/system/menu/model/MenuModel.java | 116 - .../system/menu/model/MenuModelFactory.java | 33 - .../domain/system/menu/model/RouterModel.java | 205 - .../domain/system/menu/query/MenuQuery.java | 32 - .../monitor/MonitorApplicationService.java | 87 - .../domain/system/monitor/dto/CpuInfo.java | 64 - .../domain/system/monitor/dto/DiskInfo.java | 48 - .../domain/system/monitor/dto/JvmInfo.java | 92 - .../domain/system/monitor/dto/MemoryInfo.java | 45 - .../system/monitor/dto/OnlineUserDTO.java | 64 - .../system/monitor/dto/RedisCacheInfoDTO.java | 24 - .../domain/system/monitor/dto/ServerInfo.java | 175 - .../domain/system/monitor/dto/SystemInfo.java | 38 - .../notice/NoticeApplicationService.java | 69 - .../notice/command/NoticeAddCommand.java | 30 - .../notice/command/NoticeUpdateCommand.java | 19 - .../system/notice/db/SysNoticeEntity.java | 60 - .../system/notice/db/SysNoticeMapper.java | 34 - .../system/notice/db/SysNoticeService.java | 29 - .../notice/db/SysNoticeServiceImpl.java | 24 - .../domain/system/notice/dto/NoticeDTO.java | 45 - .../system/notice/model/NoticeModel.java | 45 - .../notice/model/NoticeModelFactory.java | 35 - .../system/notice/query/NoticeQuery.java | 35 - .../system/role/RoleApplicationService.java | 148 - .../system/role/command/AddRoleCommand.java | 51 - .../role/command/UpdateRoleCommand.java | 19 - .../role/command/UpdateStatusCommand.java | 17 - .../domain/system/role/db/SysRoleEntity.java | 64 - .../domain/system/role/db/SysRoleMapper.java | 33 - .../system/role/db/SysRoleMenuEntity.java | 44 - .../system/role/db/SysRoleMenuMapper.java | 15 - .../system/role/db/SysRoleMenuService.java | 15 - .../role/db/SysRoleMenuServiceImpl.java | 18 - .../domain/system/role/db/SysRoleService.java | 59 - .../system/role/db/SysRoleServiceImpl.java | 64 - .../domain/system/role/dto/RoleDTO.java | 48 - .../domain/system/role/model/RoleModel.java | 134 - .../system/role/model/RoleModelFactory.java | 49 - .../system/role/query/AllocatedRoleQuery.java | 31 - .../domain/system/role/query/RoleQuery.java | 38 - .../role/query/UnallocatedRoleQuery.java | 32 - .../system/user/UserApplicationService.java | 259 -- .../system/user/command/AddUserCommand.java | 43 - .../user/command/ChangeStatusCommand.java | 14 - .../user/command/RegisterUserCommand.java | 40 - .../user/command/ResetPasswordCommand.java | 14 - .../user/command/UpdateProfileCommand.java | 18 - .../user/command/UpdateUserAvatarCommand.java | 21 - .../user/command/UpdateUserCommand.java | 15 - .../command/UpdateUserPasswordCommand.java | 18 - .../domain/system/user/db/SearchUserDO.java | 14 - .../domain/system/user/db/SysUserEntity.java | 97 - .../domain/system/user/db/SysUserMapper.java | 80 - .../domain/system/user/db/SysUserService.java | 84 - .../system/user/db/SysUserServiceImpl.java | 82 - .../domain/system/user/dto/UserDTO.java | 109 - .../domain/system/user/dto/UserDetailDTO.java | 25 - .../domain/system/user/dto/UserInfoDTO.java | 15 - .../system/user/dto/UserProfileDTO.java | 26 - .../domain/system/user/model/UserModel.java | 134 - .../system/user/model/UserModelFactory.java | 35 - .../system/user/query/SearchUserQuery.java | 37 - .../resources/mapper/user/SysUserMapper.xml | 7 - .../enumtype/SettlementStatusEnumTest.java | 38 - .../system/config/model/ConfigModelTest.java | 96 - .../system/menu/model/MenuModelTest.java | 96 - .../system/monitor/dto/ServerInfoTest.java | 17 - .../system/notice/model/NoticeModelTest.java | 44 - .../system/role/model/RoleModelTest.java | 71 - .../system/user/model/UserModelTest.java | 170 - .../IntegrationTestApplication.java | 18 - .../db/SysConfigServiceImplTest.java | 27 - .../db/SysMenuServiceImplTest.java | 77 - .../db/SysRoleServiceImplTest.java | 58 - .../db/SysUserServiceImplTest.java | 132 - .../src/test/resources/application-test.yml | 53 - .../src/test/resources/application.yml | 18 - backend/agileboot-infrastructure/pom.xml | 137 - .../WarDeploymentInitializer.java | 16 - .../annotations/ratelimit/RateLimit.java | 122 - .../annotations/ratelimit/RateLimitKey.java | 17 - .../ratelimit/RateLimiterAspect.java | 51 - .../AbstractRateLimitChecker.java | 17 - .../implementation/MapRateLimitChecker.java | 42 - .../implementation/RedisRateLimitChecker.java | 67 - .../unrepeatable/Unrepeatable.java | 95 - .../unrepeatable/UnrepeatableInterceptor.java | 67 - .../infrastructure/cache/RedisUtil.java | 210 - .../cache/aop/CacheNameConstants.java | 14 - .../cache/aop/GuavaCacheBean.java | 91 - .../cache/aop/RedisCacheBean.java | 70 - .../cache/aop/package-info.java | 11 - .../guava/AbstractGuavaCacheTemplate.java | 107 - .../cache/redis/CacheKeyEnum.java | 45 - .../cache/redis/RedisCacheTemplate.java | 127 - .../config/ApplicationConfig.java | 21 - .../infrastructure/config/FilterConfig.java | 80 - .../config/GlobalTransactionConfig.java | 108 - .../infrastructure/config/JacksonConfig.java | 31 - .../infrastructure/config/MyBatisConfig.java | 37 - .../config/ResourcesConfig.java | 27 - .../config/SpringDocConfig.java | 29 - .../config/captcha/CaptchaConfig.java | 97 - .../captcha/CaptchaMathTextCreator.java | 75 - .../config/redis/EmbeddedRedisConfig.java | 38 - .../config/redis/RedisConfig.java | 61 - .../exception/DbExceptionAspect.java | 73 - .../exception/GlobalExceptionFilter.java | 58 - .../exception/GlobalExceptionInterceptor.java | 106 - .../infrastructure/filter/TestFilter.java | 33 - .../infrastructure/filter/TraceIdFilter.java | 61 - .../i18n/MessageI18nCheckerRunner.java | 52 - .../infrastructure/log/MethodLogAspect.java | 85 - .../mybatisplus/CodeGenerator.java | 258 -- .../mybatisplus/CustomMetaObjectHandler.java | 62 - .../schedule/ScheduleJobManager.java | 61 - .../security/RsaKeyPairGenerator.java | 23 - .../xss/JsonHtmlXssTrimSerializer.java | 34 - .../infrastructure/thread/ShutdownHook.java | 32 - .../infrastructure/thread/ThreadConfig.java | 23 - .../thread/ThreadPoolManager.java | 72 - .../user/AuthenticationUtils.java | 85 - .../infrastructure/user/app/AppLoginUser.java | 27 - .../user/base/BaseLoginUser.java | 129 - .../infrastructure/user/base/LoginInfo.java | 36 - .../user/web/DataScopeEnum.java | 39 - .../infrastructure/user/web/RoleInfo.java | 42 - .../user/web/SystemLoginUser.java | 43 - .../src/main/resources/application-basic.yml | 129 - .../src/main/resources/banner.txt | 8 - .../main/resources/h2sql/agileboot_data.sql | 148 - .../main/resources/h2sql/agileboot_schema.sql | 141 - .../main/resources/i18n/messages.properties | 10 - .../src/main/resources/logback-spring.xml | 122 - .../main/resources/pgsql/agileboot_data.sql | 117 - .../main/resources/pgsql/agileboot_schema.sql | 134 - .../config/CaptchaMathTextCreatorTest.java | 27 - .../annotations/RateLimitTypeTest.java | 23 - .../web/util/AuthenticationUtilsTest.java | 27 - backend/mvnw | 308 -- backend/mvnw.cmd | 205 - backend/package.json | 40 + backend/pom.xml | 407 -- backend/prisma/schema.prisma | 229 + backend/sql/combine.sh | 24 - backend/sql/{agileboot.sql => init.sql} | 27 +- backend/src/app.ts | 18 + backend/src/dev.ts | 13 + backend/src/features/auth/auth.schemas.ts | 28 + backend/src/features/auth/auth.service.ts | 579 +++ backend/src/features/auth/public-config.ts | 65 + .../collaboration/collaboration.schemas.ts | 52 + .../collaboration/collaboration.service.ts | 637 +++ backend/src/features/file/file.service.ts | 43 + .../src/features/system/system-dictionary.ts | 64 + backend/src/features/system/system.service.ts | 1126 +++++ backend/src/index.ts | 2 + backend/src/routes/index.ts | 36 + backend/src/routes/modules/app-auth.ts | 54 + .../src/routes/modules/app-collaboration.ts | 62 + backend/src/routes/modules/app-profile.ts | 39 + backend/src/routes/modules/auth.ts | 61 + backend/src/routes/modules/collaboration.ts | 84 + backend/src/routes/modules/compat.ts | 36 + backend/src/routes/modules/file.ts | 40 + backend/src/routes/modules/health.ts | 8 + backend/src/routes/modules/monitor.ts | 39 + backend/src/routes/modules/system-config.ts | 49 + backend/src/routes/modules/system-log.ts | 96 + backend/src/routes/modules/system-menu.ts | 59 + backend/src/routes/modules/system-notice.ts | 61 + backend/src/routes/modules/system-profile.ts | 59 + backend/src/routes/modules/system-role.ts | 133 + backend/src/routes/modules/system-user.ts | 144 + backend/src/shared/auth/current-user.ts | 10 + backend/src/shared/auth/middleware.ts | 28 + backend/src/shared/auth/permission.ts | 106 + backend/src/shared/auth/session-store.ts | 49 + backend/src/shared/auth/token.ts | 43 + backend/src/shared/auth/types.ts | 13 + backend/src/shared/cache/redis.ts | 17 + backend/src/shared/config/env.ts | 29 + backend/src/shared/config/load-env.ts | 13 + backend/src/shared/db/prisma.ts | 16 + backend/src/shared/hono/context.ts | 8 + backend/src/shared/http/api-error.ts | 25 + backend/src/shared/http/download.ts | 30 + backend/src/shared/http/error-handler.ts | 31 + backend/src/shared/http/excel.ts | 52 + backend/src/shared/http/not-found.ts | 9 + backend/src/shared/http/response.ts | 28 + backend/src/shared/menu/support.ts | 149 + backend/tsconfig.json | 15 + docker-compose.yml | 73 +- docs/backend-feature-development.md | 188 +- frontend/eslint.base.cjs => eslint.base.cjs | 0 frontend/.prettierignore | 10 - frontend/.stylelintignore | 14 - frontend/README.md | 21 +- frontend/app/.eslintrc | 2 +- frontend/app/config/dev.ts | 4 +- frontend/app/package.json | 4 +- frontend/app/src/utils/auth.ts | 4 +- frontend/app/src/utils/http.ts | 3 +- frontend/app/tsconfig.json | 2 +- frontend/package.json | 53 - frontend/pnpm-workspace.yaml | 13 - frontend/web/.dockerignore | 21 - frontend/web/.env.production | 3 +- frontend/web/.env.staging | 3 +- frontend/web/.eslintrc.js | 2 +- frontend/web/Dockerfile | 23 - frontend/web/README.md | 167 +- frontend/web/build/info.ts | 4 +- frontend/web/index.html | 2 +- frontend/web/nginx/default.conf | 20 - frontend/web/package.json | 10 +- frontend/web/public/serverConfig.json | 2 +- frontend/web/src/utils/crypt.ts | 2 +- frontend/web/src/views/login/index.vue | 2 +- .../views/system/log/loginLog/utils/hook.tsx | 2 +- .../system/log/operationLog/utils/hook.tsx | 2 +- frontend/web/src/views/system/user/hook.tsx | 2 +- .../web/src/views/system/user/uploadForm.vue | 2 +- frontend/web/tsconfig.json | 2 +- frontend/web/types/router.d.ts | 4 +- frontend/web/vercel.json | 8 + frontend/web/vite.config.ts | 2 +- package.json | 45 + frontend/pnpm-lock.yaml => pnpm-lock.yaml | 3840 ++++++++++------- pnpm-workspace.yaml | 26 + ...ylelint.config.cjs => stylelint.config.cjs | 0 .../tsconfig.base.json => tsconfig.base.json | 1 - 457 files changed, 7397 insertions(+), 25009 deletions(-) create mode 100644 .dockerignore create mode 100644 .eslintignore rename frontend/.npmrc => .npmrc (98%) rename frontend/.eslintignore => .prettierignore (52%) rename frontend/.prettierrc.cjs => .prettierrc.cjs (100%) create mode 100644 .stylelintignore delete mode 100644 backend/.dockerignore create mode 100644 backend/.env.example create mode 100644 backend/.gitignore delete mode 100644 backend/.mvn/wrapper/maven-wrapper.jar delete mode 100644 backend/.mvn/wrapper/maven-wrapper.properties delete mode 100644 backend/Dockerfile delete mode 100644 backend/GoogleStyle.xml delete mode 100644 backend/LICENSE delete mode 100644 backend/agileboot-admin/pom.xml delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/LoginService.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/TokenService.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/UserDetailsServiceImpl.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/command/LoginCommand.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/dto/CaptchaDTO.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/dto/ConfigDTO.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/DataPermissionCheckerFactory.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/DataPermissionService.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/MenuPermissionService.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/model/AbstractDataPermissionChecker.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/model/DataCondition.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/model/checker/AllDataPermissionChecker.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/model/checker/DefaultDataPermissionChecker.java delete mode 100644 backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/permission/model/checker/OnlySelfDataPermissionChecker.java delete mode 100644 backend/agileboot-admin/src/main/resources/application-dev.yml delete mode 100644 backend/agileboot-admin/src/main/resources/application-prod.yml delete mode 100644 backend/agileboot-admin/src/main/resources/application-test.yml delete mode 100644 backend/agileboot-admin/src/main/resources/application.yml delete mode 100644 backend/agileboot-admin/src/test/java/com/agileboot/admin/config/AgileBootConfigTest.java delete mode 100644 backend/agileboot-admin/src/test/java/com/agileboot/admin/customize/service/permission/OnlySelfDataPermissionCheckerTest.java delete mode 100644 backend/agileboot-api/pom.xml delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/AgileBooApiApplication.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/controller/OrderController.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/controller/app/AppController.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/controller/common/LoginController.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/customize/config/JwtAuthenticationFilter.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/customize/config/SecurityConfig.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/customize/service/JwtTokenService.java delete mode 100644 backend/agileboot-api/src/main/java/com/agileboot/api/customize/util/ApiEncryptor.java delete mode 100644 backend/agileboot-api/src/main/resources/application.yml delete mode 100644 backend/agileboot-common/pom.xml delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelColumn.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/annotation/ExcelSheet.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/config/AgileBootConfig.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/constant/Constants.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseController.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/core/base/BaseEntity.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/core/dto/ResponseDTO.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/core/page/AbstractPageQuery.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/core/page/AbstractQuery.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/core/page/PageDTO.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/BasicEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/BasicEnumUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/DictionaryEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/BusinessTypeEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/ConfigKeyEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/GenderEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/LoginStatusEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/MenuComponentEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/MenuTypeEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/NoticeStatusEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/NoticeTypeEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/OperationStatusEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/OperatorTypeEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/RequestMethodEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/StatusEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/UserStatusEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/VisibleStatusEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/common/YesOrNoEnum.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/dictionary/CssTag.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/dictionary/Dictionary.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/enums/dictionary/DictionaryData.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/exception/ApiException.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCode.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/exception/error/ErrorCodeInterface.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/ServletHolderUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/file/FileUploadUtils.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/i18n/MessageUtils.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegion.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpRegionUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/ip/IpUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OfflineIpRegionUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/ip/OnlineIpRegionUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonException.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/jackson/JacksonUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/poi/CustomExcelUtil.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/poi/TrimXssEditor.java delete mode 100644 backend/agileboot-common/src/main/java/com/agileboot/common/utils/time/DatePickUtil.java delete mode 100644 backend/agileboot-common/src/main/resources/ip2region.xdb delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/core/exception/ApiExceptionTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/enums/BasicEnumUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/exception/error/ErrorCodeInterfaceTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/query/AbstractQueryTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/file/FileUploadUtilsTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/ip/IpRegionUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/ip/IpUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/ip/OfflineIpRegionUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/ip/OnlineIpRegionUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/jackson/JacksonUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/jackson/Person.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/poi/CustomExcelUtilTest.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/poi/PostDTO.java delete mode 100644 backend/agileboot-common/src/test/java/com/agileboot/common/utils/time/DatePickUtilTest.java delete mode 100644 backend/agileboot-domain/pom.xml delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/CollaborationRecordApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/command/AddCollaborationRecordCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/command/CollaborationExpenditureCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/command/CollaborationFileCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/command/CollaborationSettlementCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/command/CollaborationTaskCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/command/UpdateCollaborationRecordCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationExpenditureEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationExpenditureMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationExpenditureService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationExpenditureServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationFileEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationFileMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationFileService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationFileServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationRecordEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationRecordMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationRecordService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationRecordServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationSettlementEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationSettlementMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationSettlementService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationSettlementServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationTaskEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationTaskMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationTaskService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/db/CollaborationTaskServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationExpenditureDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationFileDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationMonthlyStatisticsDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationOptionDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationRecordDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationRecordDetailDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationSettlementDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/CollaborationTaskDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/dto/SettlementStatusDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/enumtype/CollaborationFileTypeEnum.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/enumtype/CollaborationOptionEnum.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/enumtype/SettlementStatusEnum.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/model/CollaborationRecordModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/model/CollaborationRecordModelFactory.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/collaboration/record/query/CollaborationRecordQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/GuavaCacheService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/MapCache.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/RedisCacheService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/command/BulkOperationCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/dto/CurrentLoginUserDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/dto/TokenDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/dto/TreeSelectedDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/dto/UploadDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/common/dto/UploadFileDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/ConfigApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/command/ConfigUpdateCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/db/SysConfigEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/db/SysConfigMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/db/SysConfigService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/db/SysConfigServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/dto/ConfigDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/model/ConfigModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/model/ConfigModelFactory.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/config/query/ConfigQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/LogApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysLoginInfoEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysLoginInfoMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysLoginInfoService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysLoginInfoServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysOperationLogEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysOperationLogMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysOperationLogService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/db/SysOperationLogServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/dto/LoginLogDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/dto/OperationLogDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/query/LoginLogQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/log/query/OperationLogQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/MenuApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/command/AddMenuCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/command/UpdateMenuCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/db/SysMenuEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/db/SysMenuMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/db/SysMenuService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/db/SysMenuServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/dto/ExtraIconDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/dto/MenuDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/dto/MenuDetailDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/dto/MetaDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/dto/RouterDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/dto/TransitionDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/model/MenuModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/model/MenuModelFactory.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/model/RouterModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/menu/query/MenuQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/MonitorApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/CpuInfo.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/DiskInfo.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/JvmInfo.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/MemoryInfo.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/OnlineUserDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/RedisCacheInfoDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/ServerInfo.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/monitor/dto/SystemInfo.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/NoticeApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/command/NoticeAddCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/command/NoticeUpdateCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/db/SysNoticeEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/db/SysNoticeMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/db/SysNoticeService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/db/SysNoticeServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/dto/NoticeDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/model/NoticeModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/model/NoticeModelFactory.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice/query/NoticeQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/RoleApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/command/AddRoleCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/command/UpdateRoleCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/command/UpdateStatusCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleMenuEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleMenuMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleMenuService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleMenuServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/db/SysRoleServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/dto/RoleDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/model/RoleModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/model/RoleModelFactory.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/query/AllocatedRoleQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/query/RoleQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role/query/UnallocatedRoleQuery.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/UserApplicationService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/AddUserCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ChangeStatusCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/RegisterUserCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/ResetPasswordCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateProfileCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserAvatarCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/command/UpdateUserPasswordCommand.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SearchUserDO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserEntity.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserMapper.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserService.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/db/SysUserServiceImpl.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserDetailDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserInfoDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/dto/UserProfileDTO.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/model/UserModel.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/model/UserModelFactory.java delete mode 100644 backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user/query/SearchUserQuery.java delete mode 100644 backend/agileboot-domain/src/main/resources/mapper/user/SysUserMapper.xml delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/collaboration/record/enumtype/SettlementStatusEnumTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/system/config/model/ConfigModelTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/system/menu/model/MenuModelTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/system/monitor/dto/ServerInfoTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/system/notice/model/NoticeModelTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/system/role/model/RoleModelTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/domain/system/user/model/UserModelTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/integrationTest/IntegrationTestApplication.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/integrationTest/db/SysConfigServiceImplTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/integrationTest/db/SysMenuServiceImplTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/integrationTest/db/SysRoleServiceImplTest.java delete mode 100644 backend/agileboot-domain/src/test/java/com/agileboot/integrationTest/db/SysUserServiceImplTest.java delete mode 100644 backend/agileboot-domain/src/test/resources/application-test.yml delete mode 100644 backend/agileboot-domain/src/test/resources/application.yml delete mode 100644 backend/agileboot-infrastructure/pom.xml delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/WarDeploymentInitializer.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/ratelimit/RateLimit.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/ratelimit/RateLimitKey.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/ratelimit/RateLimiterAspect.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/ratelimit/implementation/AbstractRateLimitChecker.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/ratelimit/implementation/MapRateLimitChecker.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/ratelimit/implementation/RedisRateLimitChecker.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/unrepeatable/Unrepeatable.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/annotations/unrepeatable/UnrepeatableInterceptor.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/RedisUtil.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/CacheNameConstants.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/GuavaCacheBean.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/RedisCacheBean.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/aop/package-info.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/guava/AbstractGuavaCacheTemplate.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/CacheKeyEnum.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/cache/redis/RedisCacheTemplate.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ApplicationConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/FilterConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/GlobalTransactionConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/JacksonConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/MyBatisConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/ResourcesConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SpringDocConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/captcha/CaptchaMathTextCreator.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/redis/EmbeddedRedisConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/redis/RedisConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/exception/DbExceptionAspect.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/exception/GlobalExceptionFilter.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/exception/GlobalExceptionInterceptor.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/TestFilter.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/filter/TraceIdFilter.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/i18n/MessageI18nCheckerRunner.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/log/MethodLogAspect.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CodeGenerator.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/mybatisplus/CustomMetaObjectHandler.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/schedule/ScheduleJobManager.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/RsaKeyPairGenerator.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/security/xss/JsonHtmlXssTrimSerializer.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ShutdownHook.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadConfig.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/thread/ThreadPoolManager.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/AuthenticationUtils.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/app/AppLoginUser.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/base/BaseLoginUser.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/base/LoginInfo.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/web/DataScopeEnum.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/web/RoleInfo.java delete mode 100644 backend/agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/user/web/SystemLoginUser.java delete mode 100644 backend/agileboot-infrastructure/src/main/resources/application-basic.yml delete mode 100644 backend/agileboot-infrastructure/src/main/resources/banner.txt delete mode 100644 backend/agileboot-infrastructure/src/main/resources/h2sql/agileboot_data.sql delete mode 100644 backend/agileboot-infrastructure/src/main/resources/h2sql/agileboot_schema.sql delete mode 100644 backend/agileboot-infrastructure/src/main/resources/i18n/messages.properties delete mode 100644 backend/agileboot-infrastructure/src/main/resources/logback-spring.xml delete mode 100644 backend/agileboot-infrastructure/src/main/resources/pgsql/agileboot_data.sql delete mode 100644 backend/agileboot-infrastructure/src/main/resources/pgsql/agileboot_schema.sql delete mode 100644 backend/agileboot-infrastructure/src/test/java/com/agileboot/framework/config/CaptchaMathTextCreatorTest.java delete mode 100644 backend/agileboot-infrastructure/src/test/java/com/agileboot/infrastructure/annotations/RateLimitTypeTest.java delete mode 100644 backend/agileboot-infrastructure/src/test/java/com/agileboot/infrastructure/web/util/AuthenticationUtilsTest.java delete mode 100755 backend/mvnw delete mode 100644 backend/mvnw.cmd create mode 100644 backend/package.json delete mode 100644 backend/pom.xml create mode 100644 backend/prisma/schema.prisma delete mode 100644 backend/sql/combine.sh rename backend/sql/{agileboot.sql => init.sql} (93%) create mode 100644 backend/src/app.ts create mode 100644 backend/src/dev.ts create mode 100644 backend/src/features/auth/auth.schemas.ts create mode 100644 backend/src/features/auth/auth.service.ts create mode 100644 backend/src/features/auth/public-config.ts create mode 100644 backend/src/features/collaboration/collaboration.schemas.ts create mode 100644 backend/src/features/collaboration/collaboration.service.ts create mode 100644 backend/src/features/file/file.service.ts create mode 100644 backend/src/features/system/system-dictionary.ts create mode 100644 backend/src/features/system/system.service.ts create mode 100644 backend/src/index.ts create mode 100644 backend/src/routes/index.ts create mode 100644 backend/src/routes/modules/app-auth.ts create mode 100644 backend/src/routes/modules/app-collaboration.ts create mode 100644 backend/src/routes/modules/app-profile.ts create mode 100644 backend/src/routes/modules/auth.ts create mode 100644 backend/src/routes/modules/collaboration.ts create mode 100644 backend/src/routes/modules/compat.ts create mode 100644 backend/src/routes/modules/file.ts create mode 100644 backend/src/routes/modules/health.ts create mode 100644 backend/src/routes/modules/monitor.ts create mode 100644 backend/src/routes/modules/system-config.ts create mode 100644 backend/src/routes/modules/system-log.ts create mode 100644 backend/src/routes/modules/system-menu.ts create mode 100644 backend/src/routes/modules/system-notice.ts create mode 100644 backend/src/routes/modules/system-profile.ts create mode 100644 backend/src/routes/modules/system-role.ts create mode 100644 backend/src/routes/modules/system-user.ts create mode 100644 backend/src/shared/auth/current-user.ts create mode 100644 backend/src/shared/auth/middleware.ts create mode 100644 backend/src/shared/auth/permission.ts create mode 100644 backend/src/shared/auth/session-store.ts create mode 100644 backend/src/shared/auth/token.ts create mode 100644 backend/src/shared/auth/types.ts create mode 100644 backend/src/shared/cache/redis.ts create mode 100644 backend/src/shared/config/env.ts create mode 100644 backend/src/shared/config/load-env.ts create mode 100644 backend/src/shared/db/prisma.ts create mode 100644 backend/src/shared/hono/context.ts create mode 100644 backend/src/shared/http/api-error.ts create mode 100644 backend/src/shared/http/download.ts create mode 100644 backend/src/shared/http/error-handler.ts create mode 100644 backend/src/shared/http/excel.ts create mode 100644 backend/src/shared/http/not-found.ts create mode 100644 backend/src/shared/http/response.ts create mode 100644 backend/src/shared/menu/support.ts create mode 100644 backend/tsconfig.json rename frontend/eslint.base.cjs => eslint.base.cjs (100%) delete mode 100644 frontend/.prettierignore delete mode 100644 frontend/.stylelintignore delete mode 100644 frontend/package.json delete mode 100644 frontend/pnpm-workspace.yaml delete mode 100644 frontend/web/.dockerignore delete mode 100644 frontend/web/Dockerfile delete mode 100644 frontend/web/nginx/default.conf create mode 100644 frontend/web/vercel.json create mode 100644 package.json rename frontend/pnpm-lock.yaml => pnpm-lock.yaml (77%) create mode 100644 pnpm-workspace.yaml rename frontend/stylelint.config.cjs => stylelint.config.cjs (100%) rename frontend/tsconfig.base.json => tsconfig.base.json (99%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2a02a83 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +node_modules +**/node_modules +.pnpm-store + +.git +.gitignore +.DS_Store +*.local +*.log + +backend/.vercel +backend/node_modules + +frontend/app/dist +frontend/app/deploy_versions +frontend/app/.temp +frontend/app/.rn_temp +frontend/app/.swc + +frontend/web/dist +frontend/web/dist-ssr +frontend/web/report.html +frontend/web/.eslintcache +frontend/web/tests/**/coverage diff --git a/.env.example b/.env.example index 4ac59f0..166decf 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,8 @@ MYSQL_ROOT_PASSWORD=root123 -MYSQL_DATABASE=todo_agileboot_pure -MYSQL_APP_USERNAME=todo_app -MYSQL_APP_PASSWORD=todo_app123 +MYSQL_DATABASE=collab_ledger +MYSQL_APP_USERNAME=collab_ledger_app +MYSQL_APP_PASSWORD=collab_ledger_app123 MYSQL_PORT=3306 REDIS_PASSWORD=redis123 REDIS_PORT=6379 - -APP_PORT=8080 -WEB_PORT=8081 -JAVA_OPTS=-Xms256m -Xmx512m diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f493682 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +node_modules +dist +build +.taro +.cache +coverage +*.d.ts +public +src/assets +frontend/web/public +frontend/web/src/assets +frontend/app/.taro +frontend/app/dist diff --git a/.gitignore b/.gitignore index 86ead69..3774679 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Common .DS_Store /.env +/node_modules/ .idea/ *.iws *.iml @@ -37,6 +38,7 @@ /frontend/web/.vscode/launch.json # Backend build tools +/backend/node_modules/ /backend/.gradle/ /backend/build/ !/backend/gradle/wrapper/gradle-wrapper.jar diff --git a/frontend/.npmrc b/.npmrc similarity index 98% rename from frontend/.npmrc rename to .npmrc index 3b89c40..e2ad808 100644 --- a/frontend/.npmrc +++ b/.npmrc @@ -1,4 +1,3 @@ shamefully-hoist=true strict-peer-dependencies=false shell-emulator=true - diff --git a/frontend/.eslintignore b/.prettierignore similarity index 52% rename from frontend/.eslintignore rename to .prettierignore index e80a802..25ddeea 100644 --- a/frontend/.eslintignore +++ b/.prettierignore @@ -4,8 +4,8 @@ build .taro .cache coverage -*.d.ts +pnpm-lock.yaml public src/assets -web/public -web/src/assets +frontend/web/public +frontend/web/src/assets diff --git a/frontend/.prettierrc.cjs b/.prettierrc.cjs similarity index 100% rename from frontend/.prettierrc.cjs rename to .prettierrc.cjs diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..efc9dc2 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,17 @@ +node_modules +dist +build +.taro +.cache +coverage +public +src/assets +src/style/reset.scss +frontend/web/public +frontend/web/src/assets +frontend/web/src/style/reset.scss +frontend/app/src/app.scss +frontend/app/src/pages/index/index.scss +src/style/reset.scss +src/app.scss +src/pages/index/index.scss diff --git a/AGENTS.md b/AGENTS.md index 4f85cd9..a6dc5c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,11 +2,10 @@ - 新增或修改代码前,必须阅读并遵循 `docs/clean-code-contract.md`。 - 后端新增或修改业务功能前,必须阅读 `docs/backend-feature-development.md`。 -- 后端代码必须遵循 `Controller -> ApplicationService -> Model -> db Service/Mapper` 的组织方式。 -- 不要把业务规则写在 Controller。 -- 不要让 Controller 直接调用 Mapper。 -- 不要直接把 Entity 或 DO 返回给前端。 -- 复杂查询结果对象可以使用 `XxxDO`,放在 `db` 包下,并由 ApplicationService 转换为 DTO。 +- 后端代码必须遵循 `Route -> Feature Service -> Prisma/Redis/shared Service` 的组织方式。 +- 不要把业务规则堆在 Route。 +- 不要在 Route 中直接扩散 Prisma 原始记录给前端。 +- 复杂查询结果由 Feature Service 转换为稳定 DTO。 - 字典类需求不要默认创建字典管理表;本项目现有字典数据使用 Enum 和缓存。 - `frontend/web` 新增或修改业务功能前,必须阅读 `docs/web-feature-development.md`。 - Web 前端接口必须通过 `@/utils/http` 封装调用,不要直接使用 Axios。 diff --git a/README.md b/README.md index 0d56a1b..c99e3c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Simple Todo +# CollabLedger -`simple-todo` 是一个前后端分离的待办/后台管理模板项目。后端基于 AgileBoot 的 Spring Boot 分层架构改造,前端包含 Web 管理端和 Taro 多端应用,根目录提供 Docker Compose 用于本地依赖启动和生产部署。 +`CollabLedger` 是一个前后端分离的协作账本/后台管理项目。后端使用 Hono,前端包含 Web 管理端和 Taro 多端应用,根目录 Docker Compose 用于启动 MySQL 和 Redis。 ## 项目架构 @@ -8,37 +8,35 @@ 当前项目是在以下开源项目基础上改造: -- 后端原仓库: -- Web 前端原仓库: +- 后端原仓库: +- Web 前端原仓库: - Web UI 上游项目: ### 目录结构 ```text -simple-todo -├── backend # Spring Boot 后端 -│ ├── agileboot-admin # 后台管理接口启动模块 -│ ├── agileboot-api # 开放接口模块 -│ ├── agileboot-common # 通用工具与基础能力 -│ ├── agileboot-domain # 业务领域模块 -│ ├── agileboot-infrastructure # 基础设施、配置、集成能力 -│ └── sql # 初始化 SQL +CollabLedger +├── backend # Hono 后端 +│ ├── prisma # Prisma schema +│ ├── sql # MySQL 初始化 SQL +│ └── src # 后端源码 ├── frontend │ ├── web # Vite + Vue 3 Web 管理端 │ └── app # Taro + Vue 3 多端应用 ├── docs # 项目开发约定 -├── docker-compose.yml # MySQL、Redis、后端、Web 编排 +├── docker-compose.yml # MySQL、Redis 编排 └── .env.example # Docker Compose 环境变量示例 ``` ## 基础环境 -- JDK 8+ -- Maven 3.8+,也可以直接使用 `backend/mvnw` 或 `backend/mvnw.cmd` - Node.js 22+,推荐配合 Corepack 使用 pnpm -- pnpm,Dockerfile 中使用 `pnpm@11.1.3` +- pnpm 11.1.3+ - Docker Desktop 或 Docker Engine + Docker Compose +项目根目录使用 pnpm workspace 统一管理 `backend`、`frontend/web` 和 +`frontend/app`。依赖安装和常用脚本都在根目录执行。 + ## 开发启动步骤 ### 1. 准备环境变量 @@ -55,7 +53,7 @@ Windows PowerShell: Copy-Item .env.example .env ``` -`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306`、`6379`,与后端 `application-dev.yml` 的本地开发配置保持一致。 +`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306`、`6379`。 ### 2. 启动 MySQL 和 Redis @@ -63,7 +61,7 @@ Copy-Item .env.example .env docker compose up -d mysql redis ``` -首次启动 MySQL 容器时,Compose 会把 `backend/sql/agileboot.sql` 挂载到 `/docker-entrypoint-initdb.d/01-agileboot.sql`,由 MySQL 镜像自动初始化数据库。 +首次启动 MySQL 容器时,Compose 会把 `backend/sql/init.sql` 挂载到 `/docker-entrypoint-initdb.d/01-init.sql`,由 MySQL 镜像自动初始化数据库。 如果数据库卷已经存在,初始化 SQL 不会重复执行。需要重建本地数据时可以执行: @@ -74,40 +72,35 @@ docker compose up -d mysql redis `docker compose down -v` 会删除本地 MySQL 和 Redis 数据卷,请确认不需要保留本地数据后再执行。 -### 3. 启动后端 - -Windows PowerShell: - -```powershell -cd backend -.\mvnw.cmd -pl agileboot-admin spring-boot:run -``` - -macOS / Linux: +### 3. 安装依赖 ```bash -cd backend -./mvnw -pl agileboot-admin spring-boot:run -``` - -后端默认地址: - -```text -http://localhost:8080 -``` - -接口文档地址: - -```text -http://localhost:8080/swagger-ui/index.html -http://localhost:8080/v3/api-docs -``` - -### 4. 启动 Web 管理端 - -```bash -cd frontend pnpm install +``` + +### 4. 启动 Hono 后端 + +本地开发地址为 `http://localhost:3000`。 + +```bash +pnpm dev:backend +``` + +如果要按 Vercel 本地模拟方式启动,需要先全局安装 Vercel CLI: + +```bash +pnpm add -g vercel +``` + +然后执行: + +```bash +pnpm dev:backend:vercel +``` + +### 5. 启动 Web 管理端 + +```bash pnpm dev:web ``` @@ -124,25 +117,27 @@ Web 开发服务默认地址: http://localhost:80 ``` +开发环境下 `frontend/web/vite.config.ts` 已默认把 `/dev-api` 代理到 `http://localhost:3000`。 + 如果 80 端口被占用,可以修改 `frontend/web/.env.development` 中的 `VITE_PORT`。 -### 5. 启动 App 端,可选 +### 6. 启动 App 端,可选 ```bash -cd frontend pnpm dev:app:weapp ``` H5 模式: ```bash -cd frontend pnpm dev:app:h5 ``` +`frontend/app/config/dev.ts` 已默认把 `TARO_APP_API_BASE` 指向 `http://localhost:3000`。 + ## 部署步骤 -生产部署推荐使用根目录的 Docker Compose。它会启动 MySQL、Redis、后端服务和 Web Nginx 服务。 +Hono 后端和 Web 管理端推荐分别部署到 Vercel 或其他 Node.js/静态托管环境。根目录 Docker Compose 只负责启动 MySQL 和 Redis。 ### 1. 准备生产环境变量 @@ -154,79 +149,122 @@ cp .env.example .env ```env MYSQL_ROOT_PASSWORD=请替换为强密码 -MYSQL_DATABASE=todo_agileboot_pure -MYSQL_APP_USERNAME=todo_app +MYSQL_DATABASE=collab_ledger +MYSQL_APP_USERNAME=collab_ledger_app MYSQL_APP_PASSWORD=请替换为强密码 MYSQL_PORT=13306 REDIS_PASSWORD=请替换为强密码 REDIS_PORT=16379 - -APP_PORT=8080 -WEB_PORT=8081 -JAVA_OPTS=-Xms256m -Xmx512m ``` -生产环境建议把 `MYSQL_PORT`、`REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306`、`16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口;后端容器仍通过 Compose 内部网络访问 MySQL `3306` 和 Redis `6379`。 +生产环境建议把 `MYSQL_PORT`、`REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306`、`16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口。 -如果数据库和 Redis 只给后端容器使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。 +如果数据库和 Redis 只给后端使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。 不要提交真实生产 `.env` 文件。 -### 2. 构建并启动完整服务 +### 2. 启动数据库和 Redis ```bash -docker compose --profile prod up -d --build +docker compose up -d mysql redis ``` -Compose 会执行以下构建: +### 3. 使用 Vercel CLI 部署后端 -- `backend/Dockerfile`:使用 Maven 构建 `agileboot-admin`,运行 Spring Boot Jar。 -- `frontend/web/Dockerfile`:构建 Web 静态文件,并用 Nginx 提供访问。 +先全局安装并登录 Vercel CLI: -Web 容器中的 Nginx 会把 `/prod-api/` 代理到 Compose 内部的后端服务 `app:8080`。 +```bash +pnpm add -g vercel +vercel login +``` -### 3. 访问服务 +后端作为单独的 Vercel Project 部署,Root Directory 为 `backend`: -默认访问地址: +```bash +cd backend +vercel link +``` + +按提示创建或关联后端项目。然后配置后端环境变量: + +```bash +vercel env add DATABASE_URL production +vercel env add REDIS_URL production +vercel env add JWT_SECRET production +vercel env add PUBLIC_FILE_BASE_URL production +``` + +常见连接串格式: + +```env +DATABASE_URL=mysql://user:password@host:3306/collab_ledger +REDIS_URL=redis://:password@host:6379 +``` + +如果 Redis 服务要求 TLS,使用 `rediss://`。Vercel Functions 不能连接本机 `127.0.0.1`、`localhost` 或 Docker 内网地址,MySQL 和 Redis 必须使用 Vercel 能访问的公网或托管服务地址。 + +部署后端: + +```bash +vercel deploy --prod +cd .. +``` + +记下部署后的后端域名,例如 `https://your-backend.vercel.app`。 + +### 4. 使用 Vercel CLI 部署 Web 管理端 + +Web 管理端作为另一个 Vercel Project 部署,Root Directory 为 `frontend/web`: + +```bash +cd frontend/web +vercel link +``` + +按提示创建或关联 Web 项目。Vercel 识别 Vite 后,构建配置保持: ```text -Web:http://localhost:8081 -后端:http://localhost:8080 +Install Command: pnpm install +Build Command: pnpm build +Output Directory: dist ``` -如果修改了 `.env` 中的 `WEB_PORT` 或 `APP_PORT`,以实际端口为准。 - -### 4. 常用运维命令 - -查看服务状态: +配置 Web 指向后端公网地址: ```bash -docker compose --profile prod ps +vercel env add VITE_APP_BASE_API production ``` -查看后端日志: +填入上一步得到的后端域名,例如 `https://your-backend.vercel.app`。 + +部署 Web: ```bash -docker compose --profile prod logs -f app +vercel deploy --prod +cd ../.. ``` -查看 Web 日志: +`frontend/web/vercel.json` 已配置前端路由回退,直接刷新管理端子路由时会返回 `index.html`。 + +### 5. 常用运维命令 + +查看数据库和 Redis 状态: ```bash -docker compose --profile prod logs -f web +docker compose ps ``` 停止服务: ```bash -docker compose --profile prod down +docker compose down ``` 停止并删除数据卷: ```bash -docker compose --profile prod down -v +docker compose down -v ``` `down -v` 会删除数据库和 Redis 数据,生产环境谨慎使用。 diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index f54f1e8..0000000 --- a/backend/.dockerignore +++ /dev/null @@ -1,12 +0,0 @@ -.git -.gitignore -.idea -*.iml -*.log -**/target -build -dist -.gradle -.mvn/wrapper/maven-wrapper.jar -.env -docker-compose.yml diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ab14bf8 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,10 @@ +DATABASE_URL="mysql://collab_ledger_app:collab_ledger_app123@127.0.0.1:3306/collab_ledger" +REDIS_URL="redis://:redis123@127.0.0.1:6379" +JWT_SECRET="sdhfkjshBN6rr32df38" +JWT_HEADER="Authorization" +JWT_AUTO_REFRESH_MINUTES="20" +UPLOAD_DIR="./uploads" +PUBLIC_FILE_BASE_URL="http://localhost:3000/profile" +CAPTCHA_ENABLED="false" +REGISTER_ENABLED="true" +RSA_PRIVATE_KEY="" diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..c8a7336 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +.vercel +.env*.local diff --git a/backend/.mvn/wrapper/maven-wrapper.jar b/backend/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index cb28b0e37c7d206feb564310fdeec0927af4123a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62547 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 A?*IS* diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d36117e..0000000 --- a/backend/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 6f8a122..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM maven:3.8.8-eclipse-temurin-8 AS build-stage - -WORKDIR /app - -COPY . . - -RUN mvn -pl agileboot-admin -am package -Dmaven.test.skip=true -B - -FROM eclipse-temurin:8-jre - -WORKDIR /app - -ENV TZ=Asia/Shanghai - -COPY --from=build-stage /app/agileboot-admin/target/*.jar /app/app.jar - -EXPOSE 8080 - -ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"] diff --git a/backend/GoogleStyle.xml b/backend/GoogleStyle.xml deleted file mode 100644 index 1214d64..0000000 --- a/backend/GoogleStyle.xml +++ /dev/null @@ -1,569 +0,0 @@ - - - diff --git a/backend/LICENSE b/backend/LICENSE deleted file mode 100644 index 50fca70..0000000 --- a/backend/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 valarchie - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/backend/README.md b/backend/README.md index 9ca2de4..b7660f0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,344 +1,108 @@ +# Backend -

- Downloads - Build Status - Build Status - Downloads - - Downloads - - - Downloads - -

-

+这是 `CollabLedger` 的后端实现,按 Vercel 官方的 Hono 方式组织:入口放在 `src/index.ts`,默认导出 `app`,部署到 `Vercel Functions` 的 `Node.js runtime`,并保持现有前端 API 协议兼容。 -logo -

-

AgileBoot v2.0.0

-

基于SpringBoot+Vue3前后端分离的Java快速开发脚手架

-

-

+## 启动 -## ⚡平台简介⚡ - -AgileBoot是一套开源的全栈精简快速开发平台,毫无保留给个人及企业免费使用。本项目的目标是做一款精简可靠,代码风格优良,项目规范的小型开发脚手架。 -适合个人开发者的小型项目或者公司内部项目使用。也可作为供初学者学习使用的案例。 - - -* 前端是基于优秀的开源项目[Pure-Admin](https://github.com/pure-admin/vue-pure-admin)开发而成。在此感谢Pure-Admin作者。 -* 前端采用Vue3、Element Plus、TypeScript、Pinia。对应前端仓库 [AgileBoot-Front-End](https://github.com/valarchie/AgileBoot-Front-End) ,保持同步更新。 -* 后端采用Spring Boot、Spring Security & Jwt、Redis & MySql、Mybatis Plus、Hutool工具包。 -* 权限认证使用Jwt,支持多终端认证系统。 -* 支持注解式主从数据库切换,注解式请求限流,注解式重复请求拦截。 -* 支持注解式菜单权限拦截,注解式数据权限拦截。 -* 支持加载动态权限菜单,实时权限控制。 -* ***有大量的单元测试,集成测试覆盖确保业务逻辑正确***。 - -***V1.0.0版本使用JS开发,V2.0.0版本使用TS开发***。 -***V1.0.0地址:[后端(AgileBoot-Back-End-Basic)](https://github.com/valarchie/AgileBoot-Back-End-Basic) - [前端(AgileBoot-Front-End-Basic)](https://github.com/valarchie/AgileBoot-Front-End-Basic)*** - -> 有任何问题或者建议,可以在 _Issues_ 中提给作者。 -> -> 您的Issue比Star更重要 -> -> 如果觉得项目对您有帮助,可以来个Star ⭐ - - -## 💥 在线体验 💥 -演示地址: -- www.agileboot.vip -- www.agileboot.cc -> 账号密码:admin/admin123 - - -## 🌴 项目背景 🌴 -业余时间想做一些个人小项目,一开始找了很多开源项目比如Ruoyi / Jeecg / ElAdmin / RenRen-Fast / Guns / EAdmin -最后本项目选择基于Ruoyi项目进行完全重构改造。 -首先非常感谢Ruoyi作者。但是Ruoyi项目存在太多缺陷。 -- 命名比较乱七八糟(很多很糟糕的命名,包括机翻英语乱用) -- 项目分包以及模块比较乱 -- 比较原始的Controller > Service > DAO的开发模式。过于面向过程。 -- 一大堆自己造的轮子,并且没有UT覆盖。 -- 大量逻辑嵌套在if else块当中 -- 值的前后不统一,比如有的地方1代表是,有的地方1代表否 -- 很多很奇怪的代码写法(比如return result > 0 ? true:false.. 一言难尽) -- 业务逻辑不集中,代码可读性较差。 - - -于是我做了大量的重构工作。 - -### 重构内容 - -- 规范: - - 切分不同环境的启动文件 - - 统一设计异常类 - - 统一设计错误码并集中处理异常 - - 统一系统内的变量并集中管理 - - 统一返回模型 - - 引入Google代码格式化模板 - - 后端代码的命名基本都整改OK - - 前端代码的命名也非常混乱,进行了整改 - - 规范系统内的常量 -- 整改: - - 引入hutool包以及guava包去掉大量自己造的轮子,尽可能使用现成的轮子 - - 去除代码中大量的warning - - 引入lombok去除大量getter setter代码 - - 调整日志级别 - - 字典类型数据完全用Enum进行代替 - - 移除SQL注入的Filter,因为迁移到Mybatis Plus就不会有这个注入的问题 - - XSS直接通过JSON序列化进行转义。 - - 替换掉很多Deprecated的类以及配置 - - 替换fastJson为Jackson - - 数据库的整体重构设计,缩减至10张表。 - - 重新设计异步代码 - - 前后端密码加密传输(更严谨的话,还是需要HTTPS) - - 重构权限校验和数据权限校验(直接都通过注解的形式) -- 优化: - - 优化异步服务 - - 优化Redis缓存类,封装各个业务缓存,提供多级缓存实现(Redis+Guava) - - 提供三个层级的缓存供使用者调用(Map,Guava,Redis使用者可依情况选择使用哪个缓存类) - - 权限判断使用多级缓存 - - IP地址查询引入离线包 - - 前端优化字典数据缓存 - - 启动优化 - - i18n支持 - - 优化excel工具类,代码更加简洁 - - 将所有逻辑集中于Domain模块中 - - 切面记录修改者和创建者 - - 统一设置事务 - -## ✨ 使用 ✨ - - -### 开发环境 - -- JDK -- Mysql -- Redis -- Node.js - -### 技术栈 - -| 技术 | 说明 | 版本 | -|----------------|-----------------|-------------------| -| `springboot` | Java项目必备框架 | 2.7 | -| `druid` | alibaba数据库连接池 | 1.2.8 | -| `springdoc` | 文档生成 | 3.0.0 | -| `mybatis-plus` | 数据库框架 | 3.5.2 | -| `hutool` | 国产工具包(简单易用) | 3.5.2 | -| `mockito` | 单元测试模拟 | 1.10.19 | -| `guava` | 谷歌工具包(提供简易缓存实现) | 31.0.1-jre | -| `junit` | 单元测试 | 1.10.19 | -| `h2` | 内存数据库 | 1.10.19 | -| `jackson` | 比较安全的Json框架 | follow springboot | -| `knife4j` | 接口文档框架 | 3.0.3 | -| `Spring Task` | 定时任务框架(适合小型项目) | follow springboot | - - -### 启动说明 - -#### 前置准备: 下载前后端代码 - -``` -git clone https://github.com/valarchie/AgileBoot-Back-End -git clone https://github.com/valarchie/AgileBoot-Front-End +```bash +pnpm install +pnpm dev:backend ``` -#### 安装好Mysql和Redis - - -#### 后端启动 -``` -1. 生成所需的数据库表 -找到后端项目根目录下的sql目录中的agileboot_xxxxx.sql脚本文件(取最新的sql文件)。 导入到你新建的数据库中。 - -2. 在admin模块底下,找到resource目录下的application-dev.yml文件 -配置数据库以及Redis的 地址、端口、账号密码 - -3. 在根目录执行mvn install - -4. 找到agileboot-admin模块中的AgileBootAdminApplication启动类,直接启动即可 - -5. 当出现以下字样即为启动成功 - ____ _ _ __ _ _ - / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || | - \___ \ | __|/ _` || '__|| __| | | | || '_ \ / __|| | | | / __|/ __|/ _ \/ __|/ __|| |_ | | | || || | - ___) || |_| (_| || | | |_ | |_| || |_) | \__ \| |_| || (__| (__| __/\__ \\__ \| _|| |_| || ||_| - |____/ \__|\__,_||_| \__| \__,_|| .__/ |___/ \__,_| \___|\___|\___||___/|___/|_| \__,_||_|(_) - |_| +默认本地地址: +```text +http://localhost:3000 ``` -#### 前端启动 -详细步骤请查看对应前端部分 - -``` -1. pnpm install - -2. pnpm run dev - -3. 当出现以下字样时即为启动成功 - -vite v2.6.14 dev server running at: - -> Local: http://127.0.0.1:80/ - -ready in 4376ms. +`pnpm dev:backend` 使用直接的 Node 开发入口: +```bash +pnpm dev:backend ``` -详细过程在这个文章中:[AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845) +如果要按 Vercel 官方方式本地模拟,需要先全局安装 Vercel CLI: - -> 对于想要尝试全栈项目的前端人员,这边提供更简便的后端启动方式,无需配置Mysql和Redis直接启动 -#### 无Mysql/Redis 后端启动 -``` -1. 找到agilboot-admin模块下的resource文件中的application.yml文件 - -2. 配置以下两个值 -spring.profiles.active: basic,dev -改为 -spring.profiles.active: basic,test - -agileboot.embedded.mysql: false -agileboot.embedded.redis: false -改为 -agileboot.embedded.mysql: true -agileboot.embedded.redis: true - -请注意:高版本的MacOS系统,无法启动内置的Redis - - -3. 找到agileboot-admin模块中的AgileBootAdminApplication启动类,直接启动即可 +```bash +pnpm add -g vercel ``` +然后执行: -## 🙊 系统内置功能 🙊 - - -🙂 大部分功能,均有通过 **单元测试** **集成测试** 保证质量。 - -| | 功能 | 描述 | -|-----|-------|---------------------------------| -| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 | -| ⭐ | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 | -| ⭐ | 岗位管理 | 配置系统用户所属担任职务 | -| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 | -| ⭐ | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | -| | 参数管理 | 对系统动态配置常用参数 | -| | 通知公告 | 系统通知公告信息发布维护 | -| 🚀 | 操作日志 | 系统正常操作日志记录和查询;系统异常信息日志记录和查询 | -| | 登录日志 | 系统登录日志记录查询包含登录异常 | -| | 在线用户 | 当前系统中活跃用户状态监控 | -| | 系统接口 | 根据业务代码自动生成相关的api接口文档 | -| | 服务监控 | 监视当前系统CPU、内存、磁盘、堆栈等相关信息 | -| | 缓存监控 | 对系统的缓存信息查询,命令统计等 | -| | 连接池监视 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 | - - -## 🐯 工程结构 🐯 - -``` -agileboot -├── agileboot-admin -- 管理后台接口模块(供后台调用) -│ -├── agileboot-api -- 开放接口模块(供客户端调用) -│ -├── agileboot-common -- 精简基础工具模块 -│ -├── agileboot-infrastructure -- 基础设施模块(主要是配置和集成,不包含业务逻辑) -│ -├── agileboot-domain -- 业务模块 -├ ├── user -- 用户模块(举例) -├ ├── command -- 命令参数接收模型(命令) -├ ├── dto -- 返回数据类 -├ ├── db -- DB操作类 -├ ├── entity -- 实体类 -├ ├── service -- DB Service -├ ├── mapper -- DB Dao -├ ├── model -- 领域模型类 -├ ├── query -- 查询参数模型(查询) -│ ├────── UserApplicationService -- 应用服务(事务层,操作领域模型类完成业务逻辑) - +```bash +pnpm dev:backend:vercel ``` -### 代码流转 +## 环境变量 -请求分为两类:一类是查询,一类是操作(即对数据有进行更新)。 +先复制: -**查询**:Controller > xxxQuery > xxxApplicationService > xxxService(Db) > xxxMapper -**操作**:Controller > xxxCommand > xxxApplicationService > xxxModel(处理逻辑) > save 或者 update (本项目直接采用JPA的方式进行插入已经更新数据) - -这是借鉴CQRS的开发理念,将查询和操作分开处理。操作类的业务实现借鉴了DDD战术设计的理念,使用领域类,工厂类更面向对象的实现逻辑。 -如果你不太适应这样的开发模式的话。可以在domain模块中按照你之前从Controller->Service->DAO的模式进行开发。it is up to you. - - - -### 二次开发指南 - -假设你要新增一个会员member业务,可以在以下三个模块新增对应的包来实现你的业务 -``` -agileboot -├── agileboot-admin -- -│ ├── member -- 会员模块 -│ -├── agileboot-domain -- -├ ├── member -- 会员模块(举例) -├ ├── command -- 命令参数接收模型(命令) -├ ├── dto -- 返回数据类 -├ ├── db -- DB操作类 -├ ├── entity -- 实体类 -├ ├── service -- DB Service -├ ├── mapper -- DB Dao -├ ├── model -- 领域模型类 -├ ├── query -- 查询参数模型(查询) -│ ├────── MemberApplicationService -- 应用服务(事务层,操作领域模型类完成业务逻辑) -└─ +```bash +cp .env.example .env ``` +最少需要配置: +- `DATABASE_URL` +- `REDIS_URL` +- `JWT_SECRET` +- `UPLOAD_DIR` ---- +## Vercel CLI 部署 -## 🎅 技术文档 🎅 -* [AgileBoot - 基于SpringBoot + Vue3的前后端快速开发脚手架](https://juejin.cn/post/7152871067151777829) -* [AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845) -* [AgileBoot - 项目内统一的错误码设计](https://juejin.cn/post/7156062116712022023) -* [AgileBoot - 如何集成内置数据库H2和内置Redis](https://juejin.cn/post/7158793441198112781) -* [AgileBoot - Mybatis Plus 框架项目落地实践总结](https://juejin.cn/post/7202573260659195963) -* [AgileBoot - SpringBoot项目多层级多环境yml设计](https://juejin.cn/post/7205171975647215676) -* [AgileBoot - 项目中多级缓存设计实践总结](https://juejin.cn/post/7208112485764857914) -* 持续输出中 +后端作为独立的 Vercel Project 部署。先全局安装并登录 Vercel CLI: +```bash +pnpm add -g vercel +vercel login +``` +进入后端目录并关联项目: -## 🌻 注意事项 🌻 -- IDEA会自动将.properties文件的编码设置为ISO-8859-1,请在Settings > Editor > File Encodings > Properties Files > 设置为UTF-8 -- 请导入统一的代码格式化模板(Google): Settings > Editor > Code Style > Java > 设置按钮 > import schema > 选择项目根目录下的GoogleStyle.xml文件 -- 如需要生成新的表,请使用CodeGenerator类进行生成。 - - 填入数据库地址,账号密码,库名。然后填入所需的表名执行代码即可。(大概看一下代码就知道怎么填啦) - - 生成的类在infrastructure模块下的target/classes目录下 - - 不同的数据库keywordsHandler方法请填入对应不同数据库handler。(搜索keywordsHandler关键字) -- 项目基础环境搭建,请参考docker目录下的指南搭建。保姆级启动说明: - - [AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845) -- 注意:管理后台的后端启动类是AgileBoot**Admin**Application -- Swagger的API地址为 http://localhost:8080/v3/api-docs +```bash +cd backend +vercel link +``` -## 🎬 AgileBoot全栈交流群 🎬 +配置生产环境变量: -QQ群: [![加入QQ群](https://img.shields.io/badge/1398880-blue.svg)](https://qm.qq.com/cgi-bin/qm/qr?k=TR5guoXS0HssErVWefmdFRirJvfpEvp1&jump_from=webapi&authKey=VkWMmVhp/pNdWuRD8sqgM+Sv2+Vy2qCJQSeLmeXlLtfER2RJBi6zL56PdcRlCmTs) 点击按钮入群。 +```bash +vercel env add DATABASE_URL production +vercel env add REDIS_URL production +vercel env add JWT_SECRET production +vercel env add PUBLIC_FILE_BASE_URL production +``` +连接串示例: -如果觉得该项目对您有帮助,可以小额捐赠支持本项目演示网站服务器等费用~ +```env +DATABASE_URL=mysql://user:password@host:3306/collab_ledger +REDIS_URL=redis://:password@host:6379 +``` +如果 Redis 服务要求 TLS,使用 `rediss://`。Vercel Functions 不能连接本机 `127.0.0.1`、`localhost` 或 Docker 内网地址。 -logo +部署: -## 💕 特别鸣谢 +```bash +vercel deploy --prod +``` +## 当前状态 -- @pokr 感谢提供ChatGpt账号助力本项目开发 +已包含: -## 💒 相关框架 -- 基于node.js开发的后端 Midwayjs +- Hono + Vercel 官方入口 +- 统一响应/错误协议 +- JWT + Redis 会话 +- 登录/注册/验证码/当前用户 +- 合作记录 CRUD +- 用户/角色/菜单/配置/公告/日志/监控接口骨架 + +仍需继续完善: + +- 动态路由 `/getRouters` +- 更完整的权限与数据范围控制 +- Excel 导出 +- 在线用户、Redis 监控细节 +- 上传文件持久化改造为对象存储或 Vercel Blob diff --git a/backend/agileboot-admin/pom.xml b/backend/agileboot-admin/pom.xml deleted file mode 100644 index d53826a..0000000 --- a/backend/agileboot-admin/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - agileboot - com.agileboot - 1.0.0 - - jar - 4.0.0 - - agileboot-admin - - - web服务入口 - - - - - - - com.agileboot - agileboot-domain - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven.surefire.plugin.version} - - - false - - - - - - - - diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java deleted file mode 100644 index 7fdfd4c..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.agileboot.admin; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.context.annotation.ComponentScan; - -/** - * 启动程序 - * 定制banner.txt的网站 - * http://patorjk.com/software/taag - * http://www.network-science.de/ascii/ - * http://www.degraeve.com/img2txt.php - * http://life.chacuo.net/convertfont2char - * @author valarchie - */ -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) -@ComponentScan(basePackages = "com.agileboot.*") -public class AgileBootAdminApplication { - - public static void main(String[] args) { - SpringApplication.run(AgileBootAdminApplication.class, args); - String successMsg = " ____ _ _ __ _ _ \n" - + " / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |\n" - + " \\___ \\ | __|/ _` || '__|| __| | | | || '_ \\ / __|| | | | / __|/ __|/ _ \\/ __|/ __|| |_ | | | || || |\n" - + " ___) || |_| (_| || | | |_ | |_| || |_) | \\__ \\| |_| || (__| (__| __/\\__ \\\\__ \\| _|| |_| || ||_|\n" - + " |____/ \\__|\\__,_||_| \\__| \\__,_|| .__/ |___/ \\__,_| \\___|\\___|\\___||___/|___/|_| \\__,_||_|(_)\n" - + " |_| "; - - System.out.println(successMsg); - } -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java deleted file mode 100644 index 20bf9f3..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.agileboot.admin.controller.app; - -import com.agileboot.admin.customize.service.login.LoginService; -import com.agileboot.admin.customize.service.login.command.LoginCommand; -import com.agileboot.admin.customize.service.login.dto.CaptchaDTO; -import com.agileboot.admin.customize.service.login.dto.ConfigDTO; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.domain.common.dto.CurrentLoginUserDTO; -import com.agileboot.domain.common.dto.TokenDTO; -import com.agileboot.domain.system.user.UserApplicationService; -import com.agileboot.domain.system.user.command.RegisterUserCommand; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author codex - */ -@Tag(name = "小程序登录API", description = "小程序登录注册相关接口") -@RestController -@RequestMapping("/app") -@RequiredArgsConstructor -public class AppAuthController { - - private final LoginService loginService; - private final UserApplicationService userApplicationService; - - @Operation(summary = "小程序配置") - @GetMapping("/getConfig") - public ResponseDTO getConfig() { - return ResponseDTO.ok(loginService.getConfig()); - } - - @Operation(summary = "小程序验证码") - @GetMapping("/captchaImage") - public ResponseDTO getCaptchaImg() { - return ResponseDTO.ok(loginService.generateCaptchaImg()); - } - - @Operation(summary = "小程序登录") - @PostMapping("/login") - public ResponseDTO login(@RequestBody LoginCommand command) { - String token = loginService.login(command); - return ResponseDTO.ok(buildTokenDTO(token)); - } - - @Operation(summary = "小程序注册") - @PostMapping("/register") - public ResponseDTO register(@Validated @RequestBody RegisterUserCommand command) { - decryptRegisterPassword(command); - loginService.validateCaptchaIfEnabled(command.getUsername(), command.getCaptchaCode(), - command.getCaptchaCodeKey()); - userApplicationService.registerUser(command); - loginService.recordRegisterInfo(command.getUsername()); - return ResponseDTO.ok(buildTokenDTO(loginService.createTokenForRegisteredUser(command.getUsername()))); - } - - @Operation(summary = "小程序当前登录用户") - @GetMapping("/getLoginUserInfo") - public ResponseDTO getLoginUserInfo() { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - return ResponseDTO.ok(userApplicationService.getLoginUserInfo(loginUser)); - } - - private TokenDTO buildTokenDTO(String token) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - CurrentLoginUserDTO currentUser = userApplicationService.getLoginUserInfo(loginUser); - return new TokenDTO(token, currentUser); - } - - private void decryptRegisterPassword(RegisterUserCommand command) { - command.setPassword(loginService.decryptPassword(command.getPassword())); - command.setConfirmPassword(loginService.decryptPassword(command.getConfirmPassword())); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java deleted file mode 100644 index c3e0044..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.agileboot.admin.controller.app; - -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.domain.collaboration.record.CollaborationRecordApplicationService; -import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand; -import com.agileboot.domain.collaboration.record.command.UpdateCollaborationRecordCommand; -import com.agileboot.domain.collaboration.record.dto.CollaborationMonthlyStatisticsDTO; -import com.agileboot.domain.collaboration.record.dto.CollaborationOptionDTO; -import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDTO; -import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDetailDTO; -import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery; -import com.agileboot.domain.common.command.BulkOperationCommand; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author codex - */ -@Tag(name = "小程序合作记录API", description = "小程序合作记录相关接口") -@RestController -@RequestMapping("/app/collaboration/record") -@Validated -@RequiredArgsConstructor -public class AppCollaborationRecordController { - - private final CollaborationRecordApplicationService recordApplicationService; - - @Operation(summary = "小程序合作记录列表") - @GetMapping("/list") - public ResponseDTO> list(CollaborationRecordQuery query) { - return ResponseDTO.ok(recordApplicationService.getRecordList(query)); - } - - @Operation(summary = "小程序合作记录详情") - @GetMapping("/{recordId}") - public ResponseDTO getInfo(@PathVariable @Positive Long recordId) { - return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId)); - } - - @Operation(summary = "小程序合作记录选项") - @GetMapping("/options") - public ResponseDTO> options() { - return ResponseDTO.ok(recordApplicationService.getOptions()); - } - - @Operation(summary = "小程序合作记录月度统计") - @GetMapping("/monthly-statistics") - public ResponseDTO> monthlyStatistics(@RequestParam Integer year) { - return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year)); - } - - @Operation(summary = "小程序新增合作记录") - @PostMapping - public ResponseDTO add(@Valid @RequestBody AddCollaborationRecordCommand command) { - recordApplicationService.addRecord(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "小程序修改合作记录") - @PutMapping - public ResponseDTO edit(@Valid @RequestBody UpdateCollaborationRecordCommand command) { - recordApplicationService.updateRecord(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "小程序删除合作记录") - @DeleteMapping - public ResponseDTO remove(@RequestParam @NotNull @NotEmpty List ids) { - recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids)); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java deleted file mode 100644 index c4ebde8..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.agileboot.admin.controller.app; - -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.domain.system.user.UserApplicationService; -import com.agileboot.domain.system.user.command.UpdateProfileCommand; -import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand; -import com.agileboot.domain.system.user.dto.UserProfileDTO; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author codex - */ -@Tag(name = "小程序个人信息API", description = "小程序个人信息相关接口") -@RestController -@RequestMapping("/app/user/profile") -@RequiredArgsConstructor -public class AppProfileController { - - private final UserApplicationService userApplicationService; - - @Operation(summary = "小程序获取个人信息") - @GetMapping - public ResponseDTO profile() { - SystemLoginUser user = AuthenticationUtils.getSystemLoginUser(); - return ResponseDTO.ok(userApplicationService.getUserProfile(user.getUserId())); - } - - @Operation(summary = "小程序修改个人信息") - @PutMapping - public ResponseDTO updateProfile(@RequestBody UpdateProfileCommand command) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - command.setUserId(loginUser.getUserId()); - userApplicationService.updateUserProfile(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "小程序修改个人密码") - @PutMapping("/password") - public ResponseDTO updatePassword(@Validated @RequestBody UpdateUserPasswordCommand command) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - command.setUserId(loginUser.getUserId()); - userApplicationService.updatePasswordBySelf(loginUser, command); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java deleted file mode 100644 index b0b4ee3..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.agileboot.admin.controller.collaboration; - -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import com.agileboot.domain.collaboration.record.CollaborationRecordApplicationService; -import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand; -import com.agileboot.domain.collaboration.record.command.UpdateCollaborationRecordCommand; -import com.agileboot.domain.collaboration.record.dto.CollaborationMonthlyStatisticsDTO; -import com.agileboot.domain.collaboration.record.dto.CollaborationOptionDTO; -import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDTO; -import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDetailDTO; -import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery; -import com.agileboot.domain.common.command.BulkOperationCommand; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.validation.Valid; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author codex - */ -@Tag(name = "合作记录API", description = "合作记录相关的增删查改和统计") -@RestController -@RequestMapping("/collaboration/record") -@Validated -@RequiredArgsConstructor -public class CollaborationRecordController extends BaseController { - - private final CollaborationRecordApplicationService recordApplicationService; - - @Operation(summary = "合作记录列表") - @PreAuthorize("@permission.has('collaboration:record:list')") - @GetMapping("/list") - public ResponseDTO> list(CollaborationRecordQuery query) { - return ResponseDTO.ok(recordApplicationService.getRecordList(query)); - } - - @Operation(summary = "合作记录详情") - @PreAuthorize("@permission.has('collaboration:record:query')") - @GetMapping("/{recordId}") - public ResponseDTO getInfo(@PathVariable @Positive Long recordId) { - return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId)); - } - - @Operation(summary = "合作记录选项") - @PreAuthorize("@permission.has('collaboration:record:list')") - @GetMapping("/options") - public ResponseDTO> options() { - return ResponseDTO.ok(recordApplicationService.getOptions()); - } - - @Operation(summary = "合作记录月度统计") - @PreAuthorize("@permission.has('collaboration:record:statistics')") - @GetMapping("/monthly-statistics") - public ResponseDTO> monthlyStatistics( - @RequestParam Integer year, @RequestParam(required = false) Long creatorId) { - return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year, creatorId)); - } - - @Operation(summary = "新增合作记录") - @PreAuthorize("@permission.has('collaboration:record:add')") - @AccessLog(title = "合作记录", businessType = BusinessTypeEnum.ADD) - @PostMapping - public ResponseDTO add(@Valid @RequestBody AddCollaborationRecordCommand command) { - recordApplicationService.addRecord(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "修改合作记录") - @PreAuthorize("@permission.has('collaboration:record:edit')") - @AccessLog(title = "合作记录", businessType = BusinessTypeEnum.MODIFY) - @PutMapping - public ResponseDTO edit(@Valid @RequestBody UpdateCollaborationRecordCommand command) { - recordApplicationService.updateRecord(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "删除合作记录") - @PreAuthorize("@permission.has('collaboration:record:remove')") - @AccessLog(title = "合作记录", businessType = BusinessTypeEnum.DELETE) - @DeleteMapping - public ResponseDTO remove(@RequestParam @NotNull @NotEmpty List ids) { - recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids)); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java deleted file mode 100644 index d6fce6b..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.agileboot.admin.controller.common; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.file.FileNameUtil; -import com.agileboot.common.constant.Constants.UploadSubDir; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.exception.ApiException; -import com.agileboot.common.exception.error.ErrorCode; -import com.agileboot.common.exception.error.ErrorCode.Business; -import com.agileboot.common.utils.ServletHolderUtil; -import com.agileboot.common.utils.file.FileUploadUtils; -import com.agileboot.common.utils.jackson.JacksonUtil; -import com.agileboot.domain.common.dto.UploadDTO; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -/** - * 通用请求处理 - * TODO 需要重构 - * @author valarchie - */ -@Tag(name = "上传API", description = "上传相关接口") -@RestController -@RequestMapping("/file") -@Slf4j -public class FileController { - - - /** - * 通用下载请求 - * download接口 其实不是很有必要 - * @param fileName 文件名称 - */ - @Operation(summary = "下载文件") - @GetMapping("/download") - public ResponseEntity fileDownload(String fileName, HttpServletResponse response) { - try { - if (!FileUploadUtils.isAllowDownload(fileName)) { - // 返回类型是ResponseEntity 不能捕获异常, 需要手动将错误填到 ResponseEntity - ResponseDTO fail = ResponseDTO.fail( - new ApiException(Business.COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD, fileName)); - return new ResponseEntity<>(JacksonUtil.to(fail).getBytes(), null, HttpStatus.OK); - } - - String filePath = FileUploadUtils.getFileAbsolutePath(UploadSubDir.DOWNLOAD_PATH, fileName); - - HttpHeaders downloadHeader = FileUploadUtils.getDownloadHeader(fileName); - - response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); - return new ResponseEntity<>(FileUtil.readBytes(filePath), downloadHeader, HttpStatus.OK); - } catch (Exception e) { - log.error("下载文件失败", e); - return null; - } - } - - /** - * 通用上传请求(单个) - */ - @Operation(summary = "单个上传文件") - @PostMapping("/upload") - public ResponseDTO uploadFile(@RequestParam("file") MultipartFile file) { - if (file == null) { - throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); - } - - // 上传并返回新文件名称 - String fileName = FileUploadUtils.upload(UploadSubDir.UPLOAD_PATH, file); - - String url = ServletHolderUtil.getContextUrl() + fileName; - - UploadDTO uploadDTO = UploadDTO.builder() - // 全路径 - .url(url) - // 相对路径 - .fileName(fileName) - // 新生成的文件名 - .newFileName(FileNameUtil.getName(fileName)) - // 原始的文件名 - .originalFilename(file.getOriginalFilename()).build(); - - return ResponseDTO.ok(uploadDTO); - } - - /** - * 通用上传请求(多个) - */ - @Operation(summary = "多个上传文件") - @PostMapping("/uploads") - public ResponseDTO> uploadFiles(@RequestParam("files") List files) { - if (CollUtil.isEmpty(files)) { - throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); - } - - List uploads = new ArrayList<>(); - - for (MultipartFile file : files) { - if (file != null) { - // 上传并返回新文件名称 - String fileName = FileUploadUtils.upload(UploadSubDir.UPLOAD_PATH, file); - String url = ServletHolderUtil.getContextUrl() + fileName; - UploadDTO uploadDTO = UploadDTO.builder() - .url(url) - .fileName(fileName) - .newFileName(FileNameUtil.getName(fileName)) - .originalFilename(file.getOriginalFilename()).build(); - - uploads.add(uploadDTO); - - } - } - return ResponseDTO.ok(uploads); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java deleted file mode 100644 index 66b513e..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.agileboot.admin.controller.common; - -import cn.hutool.core.util.StrUtil; -import com.agileboot.common.config.AgileBootConfig; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.domain.common.dto.CurrentLoginUserDTO; -import com.agileboot.domain.common.dto.TokenDTO; -import com.agileboot.domain.system.menu.MenuApplicationService; -import com.agileboot.domain.system.menu.dto.RouterDTO; -import com.agileboot.domain.system.user.UserApplicationService; -import com.agileboot.domain.system.user.command.RegisterUserCommand; -import com.agileboot.infrastructure.annotations.ratelimit.RateLimit; -import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.CacheType; -import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.LimitType; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.admin.customize.service.login.dto.CaptchaDTO; -import com.agileboot.admin.customize.service.login.dto.ConfigDTO; -import com.agileboot.admin.customize.service.login.command.LoginCommand; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.infrastructure.annotations.ratelimit.RateLimitKey; -import com.agileboot.admin.customize.service.login.LoginService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -/** - * 首页 - * - * @author valarchie - */ -@Tag(name = "登录API", description = "登录相关接口") -@RestController -@RequiredArgsConstructor -public class LoginController { - - private final LoginService loginService; - - private final MenuApplicationService menuApplicationService; - - private final UserApplicationService userApplicationService; - - private final AgileBootConfig agileBootConfig; - - /** - * 访问首页,提示语 - */ - @Operation(summary = "首页") - @GetMapping("/") - @RateLimit(key = RateLimitKey.TEST_KEY, time = 10, maxCount = 5, cacheType = CacheType.Map, - limitType = LimitType.GLOBAL) - public String index() { - return StrUtil.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", - agileBootConfig.getName(), agileBootConfig.getVersion()); - } - - - /** - * 获取系统的内置配置 - * - * @return 配置信息 - */ - @GetMapping("/getConfig") - public ResponseDTO getConfig() { - ConfigDTO configDTO = loginService.getConfig(); - return ResponseDTO.ok(configDTO); - } - - /** - * 生成验证码 - */ - @Operation(summary = "验证码") - @RateLimit(key = RateLimitKey.LOGIN_CAPTCHA_KEY, time = 10, maxCount = 10, cacheType = CacheType.REDIS, - limitType = LimitType.IP) - @GetMapping("/captchaImage") - public ResponseDTO getCaptchaImg() { - CaptchaDTO captchaImg = loginService.generateCaptchaImg(); - return ResponseDTO.ok(captchaImg); - } - - /** - * 登录方法 - * - * @param loginCommand 登录信息 - * @return 结果 - */ - @Operation(summary = "登录") - @PostMapping("/login") - public ResponseDTO login(@RequestBody LoginCommand loginCommand) { - // 生成令牌 - String token = loginService.login(loginCommand); - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser); - - return ResponseDTO.ok(new TokenDTO(token, currentUserDTO)); - } - - /** - * 获取用户信息 - * - * @return 用户信息 - */ - @Operation(summary = "获取当前登录用户信息") - @GetMapping("/getLoginUserInfo") - public ResponseDTO getLoginUserInfo() { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - - CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser); - - return ResponseDTO.ok(currentUserDTO); - } - - /** - * 获取路由信息 - * TODO 如果要在前端开启路由缓存的话 需要在ServerConfig.json 中 设置CachingAsyncRoutes=true 避免一直重复请求路由接口 - * @return 路由信息 - */ - @Operation(summary = "获取用户对应的菜单路由", description = "用于动态生成路由") - @GetMapping("/getRouters") - public ResponseDTO> getRouters() { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - List routerTree = menuApplicationService.getRouterTree(loginUser); - return ResponseDTO.ok(routerTree); - } - - - @Operation(summary = "注册接口") - @PostMapping("/register") - public ResponseDTO register(@Validated @RequestBody RegisterUserCommand command) { - decryptRegisterPassword(command); - loginService.validateCaptchaIfEnabled(command.getUsername(), command.getCaptchaCode(), command.getCaptchaCodeKey()); - userApplicationService.registerUser(command); - loginService.recordRegisterInfo(command.getUsername()); - - String token = loginService.createTokenForRegisteredUser(command.getUsername()); - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser); - - return ResponseDTO.ok(new TokenDTO(token, currentUserDTO)); - } - - private void decryptRegisterPassword(RegisterUserCommand command) { - command.setPassword(loginService.decryptPassword(command.getPassword())); - command.setConfirmPassword(loginService.decryptPassword(command.getConfirmPassword())); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java deleted file mode 100644 index a0d29cf..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.agileboot.admin.controller.system; - -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.domain.common.cache.CacheCenter; -import com.agileboot.domain.system.monitor.MonitorApplicationService; -import com.agileboot.domain.system.monitor.dto.OnlineUserDTO; -import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO; -import com.agileboot.domain.system.monitor.dto.ServerInfo; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 缓存监控 - * - * @author valarchie - */ -@Tag(name = "监控API", description = "监控相关信息") -@RestController -@RequestMapping("/monitor") -@RequiredArgsConstructor -public class MonitorController extends BaseController { - - private final MonitorApplicationService monitorApplicationService; - - @Operation(summary = "Redis信息") - @PreAuthorize("@permission.has('monitor:cache:list')") - @GetMapping("/cacheInfo") - public ResponseDTO getRedisCacheInfo() { - RedisCacheInfoDTO redisCacheInfo = monitorApplicationService.getRedisCacheInfo(); - return ResponseDTO.ok(redisCacheInfo); - } - - - @Operation(summary = "服务器信息") - @PreAuthorize("@permission.has('monitor:server:list')") - @GetMapping("/serverInfo") - public ResponseDTO getServerInfo() { - ServerInfo serverInfo = monitorApplicationService.getServerInfo(); - return ResponseDTO.ok(serverInfo); - } - - /** - * 获取在线用户列表 - * - * @param ipAddress ip地址 - * @param username 用户名 - * @return 分页处理后的在线用户信息 - */ - @Operation(summary = "在线用户列表") - @PreAuthorize("@permission.has('monitor:online:list')") - @GetMapping("/onlineUsers") - public ResponseDTO> onlineUsers(String ipAddress, String username) { - List onlineUserList = monitorApplicationService.getOnlineUserList(username, ipAddress); - return ResponseDTO.ok(new PageDTO<>(onlineUserList)); - } - - /** - * 强退用户 - */ - @Operation(summary = "强退用户") - @PreAuthorize("@permission.has('monitor:online:forceLogout')") - @AccessLog(title = "在线用户", businessType = BusinessTypeEnum.FORCE_LOGOUT) - @DeleteMapping("/onlineUser/{tokenId}") - public ResponseDTO logoutOnlineUser(@PathVariable String tokenId) { - CacheCenter.loginUserCache.delete(tokenId); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java deleted file mode 100644 index 38072ab..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.agileboot.admin.controller.system; - -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.domain.common.cache.CacheCenter; -import com.agileboot.domain.system.config.ConfigApplicationService; -import com.agileboot.domain.system.config.command.ConfigUpdateCommand; -import com.agileboot.domain.system.config.dto.ConfigDTO; -import com.agileboot.domain.system.config.query.ConfigQuery; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 参数配置 信息操作处理 - * @author valarchie - */ -@RestController -@RequestMapping("/system") -@Validated -@RequiredArgsConstructor -@Tag(name = "配置API", description = "配置相关的增删查改") -public class SysConfigController extends BaseController { - - private final ConfigApplicationService configApplicationService; - - /** - * 获取参数配置列表 - */ - @Operation(summary = "参数列表", description = "分页获取配置参数列表") - @PreAuthorize("@permission.has('system:config:list')") - @GetMapping("/configs") - public ResponseDTO> list(ConfigQuery query) { - PageDTO page = configApplicationService.getConfigList(query); - return ResponseDTO.ok(page); - } - - /** - * 根据参数编号获取详细信息 - */ - @PreAuthorize("@permission.has('system:config:query')") - @GetMapping(value = "/config/{configId}") - @Operation(summary = "配置信息", description = "配置的详细信息") - public ResponseDTO getInfo(@NotNull @Positive @PathVariable Long configId) { - ConfigDTO config = configApplicationService.getConfigInfo(configId); - return ResponseDTO.ok(config); - } - - - /** - * 修改参数配置 - */ - @PreAuthorize("@permission.has('system:config:edit')") - @AccessLog(title = "参数管理", businessType = BusinessTypeEnum.MODIFY) - @Operation(summary = "配置修改", description = "配置修改") - @PutMapping(value = "/config/{configId}") - public ResponseDTO edit(@NotNull @Positive @PathVariable Long configId, @RequestBody ConfigUpdateCommand config) { - config.setConfigId(configId); - configApplicationService.updateConfig(config); - return ResponseDTO.ok(); - } - - /** - * 刷新参数缓存 - */ - @Operation(summary = "刷新配置缓存") - @PreAuthorize("@permission.has('system:config:remove')") - @AccessLog(title = "参数管理", businessType = BusinessTypeEnum.CLEAN) - @DeleteMapping("/configs/cache") - public ResponseDTO refreshCache() { - CacheCenter.configCache.invalidateAll(); - return ResponseDTO.ok(); - } -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java deleted file mode 100644 index 741cc09..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.agileboot.admin.controller.system; - - -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.common.utils.poi.CustomExcelUtil; -import com.agileboot.domain.common.command.BulkOperationCommand; -import com.agileboot.domain.system.log.LogApplicationService; -import com.agileboot.domain.system.log.dto.LoginLogDTO; -import com.agileboot.domain.system.log.query.LoginLogQuery; -import com.agileboot.domain.system.log.dto.OperationLogDTO; -import com.agileboot.domain.system.log.query.OperationLogQuery; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * 系统访问记录 - * - * @author valarchie - */ -@Tag(name = "日志API", description = "日志相关API") -@RestController -@RequestMapping("/logs") -@Validated -@RequiredArgsConstructor -public class SysLogsController extends BaseController { - - private final LogApplicationService logApplicationService; - - @Operation(summary = "登录日志列表") - @PreAuthorize("@permission.has('monitor:logininfor:list')") - @GetMapping("/loginLogs") - public ResponseDTO> loginInfoList(LoginLogQuery query) { - PageDTO pageDTO = logApplicationService.getLoginInfoList(query); - return ResponseDTO.ok(pageDTO); - } - - @Operation(summary = "登录日志导出", description = "将登录日志导出到excel") - @AccessLog(title = "登录日志", businessType = BusinessTypeEnum.EXPORT) - @PreAuthorize("@permission.has('monitor:logininfor:export')") - @GetMapping("/loginLogs/excel") - public void loginInfosExcel(HttpServletResponse response, LoginLogQuery query) { - PageDTO pageDTO = logApplicationService.getLoginInfoList(query); - CustomExcelUtil.writeToResponse(pageDTO.getRows(), LoginLogDTO.class, response); - } - - @Operation(summary = "删除登录日志") - @PreAuthorize("@permission.has('monitor:logininfor:remove')") - @AccessLog(title = "登录日志", businessType = BusinessTypeEnum.DELETE) - @DeleteMapping("/loginLogs") - public ResponseDTO removeLoginInfos(@RequestParam @NotNull @NotEmpty List ids) { - logApplicationService.deleteLoginInfo(new BulkOperationCommand<>(ids)); - return ResponseDTO.ok(); - } - - @Operation(summary = "操作日志列表") - @PreAuthorize("@permission.has('monitor:operlog:list')") - @GetMapping("/operationLogs") - public ResponseDTO> operationLogs(OperationLogQuery query) { - PageDTO pageDTO = logApplicationService.getOperationLogList(query); - return ResponseDTO.ok(pageDTO); - } - -// @GetMapping("/download") -// public ResponseEntity downloadFile() throws IOException { -// // 从文件系统或其他位置获取文件输入流 -// File file = new File("path/to/file"); -// InputStream inputStream = new FileInputStream(file); -// CustomExcelUtil.wri -// -// // 创建一个 InputStreamResource 对象,将文件输入流包装在其中 -// InputStreamResource resource = new InputStreamResource(inputStream); -// -// // 返回 ResponseEntity 对象,其中包含 InputStreamResource 对象和文件名 -// return ResponseEntity.ok() -// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName()) -// .contentType(MediaType.APPLICATION_OCTET_STREAM) -// .contentLength(file.length()) -// .body(resource); -// } - /** - * 可否改成以上的形式 TODO - * @param response - * @param query - */ - @Operation(summary = "操作日志导出") - @AccessLog(title = "操作日志", businessType = BusinessTypeEnum.EXPORT) - @PreAuthorize("@permission.has('monitor:operlog:export')") - @GetMapping("/operationLogs/excel") - public void operationLogsExcel(HttpServletResponse response, OperationLogQuery query) { - PageDTO pageDTO = logApplicationService.getOperationLogList(query); - CustomExcelUtil.writeToResponse(pageDTO.getRows(), OperationLogDTO.class, response); - } - - @Operation(summary = "删除操作日志") - @AccessLog(title = "操作日志", businessType = BusinessTypeEnum.DELETE) - @PreAuthorize("@permission.has('monitor:operlog:remove')") - @DeleteMapping("/operationLogs") - public ResponseDTO removeOperationLogs(@RequestParam List operationIds) { - logApplicationService.deleteOperationLog(new BulkOperationCommand<>(operationIds)); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java deleted file mode 100644 index 9469f7e..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.agileboot.admin.controller.system; - -import cn.hutool.core.lang.tree.Tree; -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.domain.system.menu.MenuApplicationService; -import com.agileboot.domain.system.menu.command.AddMenuCommand; -import com.agileboot.domain.system.menu.command.UpdateMenuCommand; -import com.agileboot.domain.system.menu.dto.MenuDTO; -import com.agileboot.domain.system.menu.dto.MenuDetailDTO; -import com.agileboot.domain.system.menu.query.MenuQuery; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.PositiveOrZero; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 菜单信息 - * - * @author valarchie - */ -@Tag(name = "菜单API", description = "菜单相关的增删查改") -@RestController -@RequestMapping("/system/menus") -@Validated -@RequiredArgsConstructor -public class SysMenuController extends BaseController { - - private final MenuApplicationService menuApplicationService; - - /** - * 获取菜单列表 - */ - @Operation(summary = "菜单列表") - @PreAuthorize("@permission.has('system:menu:list')") - @GetMapping - public ResponseDTO> menuList(MenuQuery menuQuery) { - List menuList = menuApplicationService.getMenuList(menuQuery); - return ResponseDTO.ok(menuList); - } - - /** - * 根据菜单编号获取详细信息 - */ - @Operation(summary = "菜单详情") - @PreAuthorize("@permission.has('system:menu:query')") - @GetMapping(value = "/{menuId}") - public ResponseDTO menuInfo(@PathVariable @NotNull @PositiveOrZero Long menuId) { - MenuDetailDTO menu = menuApplicationService.getMenuInfo(menuId); - return ResponseDTO.ok(menu); - } - - /** - * 获取菜单下拉树列表 - */ - @Operation(summary = "菜单列表(树级)", description = "菜单树级下拉框") - @GetMapping("/dropdown") - public ResponseDTO>> dropdownList() { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - List> dropdownList = menuApplicationService.getDropdownList(loginUser); - return ResponseDTO.ok(dropdownList); - } - - /** - * 新增菜单 - * 需支持一级菜单以及 多级菜单 子菜单为一个 或者 多个的情况 - * 隐藏菜单不显示 以及rank排序 - * 内链 和 外链 - */ - @Operation(summary = "添加菜单") - @PreAuthorize("@permission.has('system:menu:add')") - @AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.ADD) - @PostMapping - public ResponseDTO add(@RequestBody AddMenuCommand addCommand) { - menuApplicationService.addMenu(addCommand); - return ResponseDTO.ok(); - } - - /** - * 修改菜单 - */ - @Operation(summary = "编辑菜单") - @PreAuthorize("@permission.has('system:menu:edit')") - @AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/{menuId}") - public ResponseDTO edit(@PathVariable("menuId") Long menuId, @RequestBody UpdateMenuCommand updateCommand) { - updateCommand.setMenuId(menuId); - menuApplicationService.updateMenu(updateCommand); - return ResponseDTO.ok(); - } - - /** - * 删除菜单 - */ - @Operation(summary = "删除菜单") - @PreAuthorize("@permission.has('system:menu:remove')") - @AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.DELETE) - @DeleteMapping("/{menuId}") - public ResponseDTO remove(@PathVariable("menuId") Long menuId) { - menuApplicationService.remove(menuId); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java deleted file mode 100644 index 2e149c0..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.agileboot.admin.controller.system; - -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.domain.common.command.BulkOperationCommand; -import com.agileboot.domain.system.notice.NoticeApplicationService; -import com.agileboot.domain.system.notice.command.NoticeAddCommand; -import com.agileboot.domain.system.notice.command.NoticeUpdateCommand; -import com.agileboot.domain.system.notice.dto.NoticeDTO; -import com.agileboot.domain.system.notice.query.NoticeQuery; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.infrastructure.annotations.unrepeatable.Unrepeatable; -import com.agileboot.infrastructure.annotations.unrepeatable.Unrepeatable.CheckType; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import com.baomidou.dynamic.datasource.annotation.DS; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * 公告 信息操作处理 - * - * @author valarchie - */ -@Tag(name = "公告API", description = "公告相关的增删查改") -@RestController -@RequestMapping("/system/notices") -@Validated -@RequiredArgsConstructor -public class SysNoticeController extends BaseController { - - private final NoticeApplicationService noticeApplicationService; - - /** - * 获取通知公告列表 - */ - @Operation(summary = "公告列表") - @PreAuthorize("@permission.has('system:notice:list')") - @GetMapping - public ResponseDTO> list(NoticeQuery query) { - PageDTO pageDTO = noticeApplicationService.getNoticeList(query); - return ResponseDTO.ok(pageDTO); - } - - /** - * 获取通知公告列表 - * 从从库获取数据 例子 仅供参考 - */ - @Operation(summary = "公告列表(从数据库从库获取)", description = "演示主从库的例子") - @DS("slave") - @PreAuthorize("@permission.has('system:notice:list')") - @GetMapping("/database/slave") - public ResponseDTO> listFromSlave(NoticeQuery query) { - PageDTO pageDTO = noticeApplicationService.getNoticeList(query); - return ResponseDTO.ok(pageDTO); - } - - /** - * 根据通知公告编号获取详细信息 - */ - @Operation(summary = "公告详情") - @PreAuthorize("@permission.has('system:notice:query')") - @GetMapping(value = "/{noticeId}") - public ResponseDTO getInfo(@PathVariable @NotNull @Positive Long noticeId) { - return ResponseDTO.ok(noticeApplicationService.getNoticeInfo(noticeId)); - } - - /** - * 新增通知公告 - */ - @Operation(summary = "添加公告") - @Unrepeatable(interval = 60, checkType = CheckType.SYSTEM_USER) - @PreAuthorize("@permission.has('system:notice:add')") - @AccessLog(title = "通知公告", businessType = BusinessTypeEnum.ADD) - @PostMapping - public ResponseDTO add(@RequestBody NoticeAddCommand addCommand) { - noticeApplicationService.addNotice(addCommand); - return ResponseDTO.ok(); - } - - /** - * 修改通知公告 - */ - @Operation(summary = "修改公告") - @PreAuthorize("@permission.has('system:notice:edit')") - @AccessLog(title = "通知公告", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/{noticeId}") - public ResponseDTO edit(@PathVariable Long noticeId, @RequestBody NoticeUpdateCommand updateCommand) { - updateCommand.setNoticeId(noticeId); - noticeApplicationService.updateNotice(updateCommand); - return ResponseDTO.ok(); - } - - /** - * 删除通知公告 - */ - @Operation(summary = "删除公告") - @PreAuthorize("@permission.has('system:notice:remove')") - @AccessLog(title = "通知公告", businessType = BusinessTypeEnum.DELETE) - @DeleteMapping - public ResponseDTO remove(@RequestParam List noticeIds) { - noticeApplicationService.deleteNotice(new BulkOperationCommand<>(noticeIds)); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java deleted file mode 100644 index 7a4a8a2..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.agileboot.admin.controller.system; - -import com.agileboot.common.constant.Constants.UploadSubDir; -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.exception.ApiException; -import com.agileboot.common.exception.error.ErrorCode; -import com.agileboot.common.utils.file.FileUploadUtils; -import com.agileboot.domain.common.dto.UploadFileDTO; -import com.agileboot.domain.system.user.UserApplicationService; -import com.agileboot.domain.system.user.command.UpdateProfileCommand; -import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand; -import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand; -import com.agileboot.domain.system.user.dto.UserProfileDTO; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -/** - * 个人信息 业务处理 - * - * @author ruoyi - */ -@Tag(name = "个人信息API", description = "个人信息相关接口") -@RestController -@RequestMapping("/system/user/profile") -@RequiredArgsConstructor -public class SysProfileController extends BaseController { - - private final UserApplicationService userApplicationService; - - /** - * 个人信息 - */ - @Operation(summary = "获取个人信息") - @GetMapping - public ResponseDTO profile() { - SystemLoginUser user = AuthenticationUtils.getSystemLoginUser(); - UserProfileDTO userProfile = userApplicationService.getUserProfile(user.getUserId()); - return ResponseDTO.ok(userProfile); - } - - /** - * 修改用户 - */ - @Operation(summary = "修改个人信息") - @AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY) - @PutMapping - public ResponseDTO updateProfile(@RequestBody UpdateProfileCommand command) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - command.setUserId(loginUser.getUserId()); - userApplicationService.updateUserProfile(command); - return ResponseDTO.ok(); - } - - /** - * 重置密码 - */ - @Operation(summary = "重置个人密码") - @AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/password") - public ResponseDTO updatePassword(@Validated @RequestBody UpdateUserPasswordCommand command) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - command.setUserId(loginUser.getUserId()); - userApplicationService.updatePasswordBySelf(loginUser, command); - return ResponseDTO.ok(); - } - - /** - * 头像上传 - */ - @Operation(summary = "修改个人头像") - @AccessLog(title = "用户头像", businessType = BusinessTypeEnum.MODIFY) - @PostMapping("/avatar") - public ResponseDTO avatar(@RequestParam("avatarfile") MultipartFile file) { - if (file.isEmpty()) { - throw new ApiException(ErrorCode.Business.USER_UPLOAD_FILE_FAILED); - } - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - String avatarUrl = FileUploadUtils.upload(UploadSubDir.AVATAR_PATH, file); - - userApplicationService.updateUserAvatar(new UpdateUserAvatarCommand(loginUser.getUserId(), avatarUrl)); - return ResponseDTO.ok(new UploadFileDTO(avatarUrl)); - } -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java deleted file mode 100644 index ba3e709..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.agileboot.admin.controller.system; - -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.common.utils.poi.CustomExcelUtil; -import com.agileboot.domain.system.role.RoleApplicationService; -import com.agileboot.domain.system.role.command.AddRoleCommand; -import com.agileboot.domain.system.role.command.UpdateRoleCommand; -import com.agileboot.domain.system.role.command.UpdateStatusCommand; -import com.agileboot.domain.system.role.dto.RoleDTO; -import com.agileboot.domain.system.role.query.AllocatedRoleQuery; -import com.agileboot.domain.system.role.query.RoleQuery; -import com.agileboot.domain.system.role.query.UnallocatedRoleQuery; -import com.agileboot.domain.system.user.dto.UserDTO; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import javax.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 角色信息 - * - * @author valarchie - */ -@Tag(name = "角色API", description = "角色相关的增删查改") -@RestController -@RequestMapping("/system/role") -@Validated -@RequiredArgsConstructor -public class SysRoleController extends BaseController { - - private final RoleApplicationService roleApplicationService; - - @Operation(summary = "角色列表") - @PreAuthorize("@permission.has('system:role:list')") - @GetMapping("/list") - public ResponseDTO> list(RoleQuery query) { - PageDTO pageDTO = roleApplicationService.getRoleList(query); - return ResponseDTO.ok(pageDTO); - } - - @Operation(summary = "角色列表导出") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.EXPORT) - @PreAuthorize("@permission.has('system:role:export')") - @PostMapping("/export") - public void export(HttpServletResponse response, RoleQuery query) { - PageDTO pageDTO = roleApplicationService.getRoleList(query); - CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response); - } - - /** - * 根据角色编号获取详细信息 - */ - @Operation(summary = "角色详情") - @PreAuthorize("@permission.has('system:role:query')") - @GetMapping(value = "/{roleId}") - public ResponseDTO getInfo(@PathVariable @NotNull Long roleId) { - RoleDTO roleInfo = roleApplicationService.getRoleInfo(roleId); - return ResponseDTO.ok(roleInfo); - } - - /** - * 新增角色 - */ - @Operation(summary = "添加角色") - @PreAuthorize("@permission.has('system:role:add')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.ADD) - @PostMapping - public ResponseDTO add(@RequestBody AddRoleCommand addCommand) { - roleApplicationService.addRole(addCommand); - return ResponseDTO.ok(); - } - - /** - * 移除角色 - */ - @Operation(summary = "删除角色") - @PreAuthorize("@permission.has('system:role:remove')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.DELETE) - @DeleteMapping(value = "/{roleId}") - public ResponseDTO remove(@PathVariable("roleId") List roleIds) { - roleApplicationService.deleteRoleByBulk(roleIds); - return ResponseDTO.ok(); - } - - /** - * 修改保存角色 - */ - @Operation(summary = "修改角色") - @PreAuthorize("@permission.has('system:role:edit')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping - public ResponseDTO edit(@Validated @RequestBody UpdateRoleCommand updateCommand) { - roleApplicationService.updateRole(updateCommand); - return ResponseDTO.ok(); - } - - /** - * 角色状态修改 - */ - @Operation(summary = "修改角色状态") - @PreAuthorize("@permission.has('system:role:edit')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/{roleId}/status") - public ResponseDTO changeStatus(@PathVariable("roleId") Long roleId, - @RequestBody UpdateStatusCommand command) { - command.setRoleId(roleId); - - roleApplicationService.updateStatus(command); - return ResponseDTO.ok(); - } - - - /** - * 查询已分配用户角色列表 - */ - @Operation(summary = "已关联该角色的用户列表") - @PreAuthorize("@permission.has('system:role:list')") - @GetMapping("/{roleId}/allocated/list") - public ResponseDTO> allocatedUserList(@PathVariable("roleId") Long roleId, - AllocatedRoleQuery query) { - query.setRoleId(roleId); - PageDTO page = roleApplicationService.getAllocatedUserList(query); - return ResponseDTO.ok(page); - } - - /** - * 查询未分配用户角色列表 - */ - @Operation(summary = "未关联该角色的用户列表") - @PreAuthorize("@permission.has('system:role:list')") - @GetMapping("/{roleId}/unallocated/list") - public ResponseDTO> unallocatedUserList(@PathVariable("roleId") Long roleId, - UnallocatedRoleQuery query) { - query.setRoleId(roleId); - PageDTO page = roleApplicationService.getUnallocatedUserList(query); - return ResponseDTO.ok(page); - } - - - /** - * 批量取消授权用户 - */ - @Operation(summary = "批量解除角色和用户的关联") - @PreAuthorize("@permission.has('system:role:edit')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.GRANT) - @DeleteMapping("/users/{userIds}/grant/bulk") - public ResponseDTO deleteRoleOfUserByBulk(@PathVariable("userIds") List userIds) { - roleApplicationService.deleteRoleOfUserByBulk(userIds); - return ResponseDTO.ok(); - } - - /** - * 批量选择用户授权 - */ - @Operation(summary = "批量添加用户和角色关联") - @PreAuthorize("@permission.has('system:role:edit')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.GRANT) - @PostMapping("/{roleId}/users/{userIds}/grant/bulk") - public ResponseDTO addRoleForUserByBulk(@PathVariable("roleId") Long roleId, - @PathVariable("userIds") List userIds) { - roleApplicationService.addRoleOfUserByBulk(roleId, userIds); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java deleted file mode 100644 index 722bf2f..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.agileboot.admin.controller.system; - -import cn.hutool.core.collection.ListUtil; -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.core.page.PageDTO; -import com.agileboot.common.utils.poi.CustomExcelUtil; -import com.agileboot.domain.common.command.BulkOperationCommand; -import com.agileboot.domain.system.user.UserApplicationService; -import com.agileboot.domain.system.user.command.AddUserCommand; -import com.agileboot.domain.system.user.command.ChangeStatusCommand; -import com.agileboot.domain.system.user.command.ResetPasswordCommand; -import com.agileboot.domain.system.user.command.UpdateUserCommand; -import com.agileboot.domain.system.user.dto.UserDTO; -import com.agileboot.domain.system.user.dto.UserDetailDTO; -import com.agileboot.domain.system.user.query.SearchUserQuery; -import com.agileboot.admin.customize.aop.accessLog.AccessLog; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.common.enums.common.BusinessTypeEnum; -import com.agileboot.domain.system.user.db.SearchUserDO; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -/** - * 用户信息 - * @author valarchie - */ -@Tag(name = "用户API", description = "用户相关的增删查改") -@RestController -@RequestMapping("/system/users") -@RequiredArgsConstructor -public class SysUserController extends BaseController { - - private final UserApplicationService userApplicationService; - - /** - * 获取用户列表 - */ - @Operation(summary = "用户列表") - @PreAuthorize("@permission.has('system:user:list')") - @GetMapping - public ResponseDTO> userList(SearchUserQuery query) { - PageDTO page = userApplicationService.getUserList(query); - return ResponseDTO.ok(page); - } - - @Operation(summary = "用户列表导出") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.EXPORT) - @PreAuthorize("@permission.has('system:user:export')") - @GetMapping("/excel") - public void exportUserByExcel(HttpServletResponse response, SearchUserQuery query) { - PageDTO userList = userApplicationService.getUserList(query); - CustomExcelUtil.writeToResponse(userList.getRows(), UserDTO.class, response); - } - - @Operation(summary = "用户列表导入") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.IMPORT) - @PreAuthorize("@permission.has('system:user:import')") - @PostMapping("/excel") - public ResponseDTO importUserByExcel(MultipartFile file) { - List commands = CustomExcelUtil.readFromRequest(AddUserCommand.class, file); - - for (AddUserCommand command : commands) { - userApplicationService.addUser(command); - } - return ResponseDTO.ok(); - } - - /** - * 下载批量导入模板 - */ - @Operation(summary = "用户导入excel下载") - @GetMapping("/excelTemplate") - public void downloadExcelTemplate(HttpServletResponse response) { - CustomExcelUtil.writeToResponse(ListUtil.toList(new AddUserCommand()), AddUserCommand.class, response); - } - - /** - * 根据用户编号获取详细信息 - */ - @Operation(summary = "用户详情") - @PreAuthorize("@permission.has('system:user:query')") - @GetMapping("/{userId}") - public ResponseDTO getUserDetailInfo(@PathVariable(value = "userId", required = false) Long userId) { - UserDetailDTO userDetailInfo = userApplicationService.getUserDetailInfo(userId); - return ResponseDTO.ok(userDetailInfo); - } - - /** - * 新增用户 - */ - @Operation(summary = "新增用户") - @PreAuthorize("@permission.has('system:user:add')") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.ADD) - @PostMapping - public ResponseDTO add(@Validated @RequestBody AddUserCommand command) { - userApplicationService.addUser(command); - return ResponseDTO.ok(); - } - - /** - * 修改用户 - */ - @Operation(summary = "修改用户") - @PreAuthorize("@permission.has('system:user:edit') AND @dataScope.checkUserId(#command.userId)") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/{userId}") - public ResponseDTO edit(@Validated @RequestBody UpdateUserCommand command) { - userApplicationService.updateUser(command); - return ResponseDTO.ok(); - } - - /** - * 删除用户 - */ - @Operation(summary = "删除用户") - @PreAuthorize("@permission.has('system:user:remove') AND @dataScope.checkUserIds(#userIds)") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.DELETE) - @DeleteMapping("/{userIds}") - public ResponseDTO remove(@PathVariable List userIds) { - BulkOperationCommand bulkDeleteCommand = new BulkOperationCommand<>(userIds); - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - userApplicationService.deleteUsers(loginUser, bulkDeleteCommand); - return ResponseDTO.ok(); - } - - /** - * 重置密码 - */ - @Operation(summary = "重置用户密码") - @PreAuthorize("@permission.has('system:user:resetPwd') AND @dataScope.checkUserId(#userId)") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/{userId}/password") - public ResponseDTO resetPassword(@PathVariable Long userId, @RequestBody ResetPasswordCommand command) { - command.setUserId(userId); - userApplicationService.resetUserPassword(command); - return ResponseDTO.ok(); - } - - /** - * 状态修改 - */ - @Operation(summary = "修改用户状态") - @PreAuthorize("@permission.has('system:user:edit') AND @dataScope.checkUserId(#command.userId)") - @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping("/{userId}/status") - public ResponseDTO changeStatus(@PathVariable Long userId, @RequestBody ChangeStatusCommand command) { - command.setUserId(userId); - userApplicationService.changeUserStatus(command); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java deleted file mode 100644 index a00cd7c..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.agileboot.admin.customize.aop.accessLog; - -import com.agileboot.common.enums.common.BusinessTypeEnum; -import com.agileboot.common.enums.common.OperatorTypeEnum; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 自定义操作日志记录注解 - * - * @author ruoyi - */ -@Target({ElementType.PARAMETER, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface AccessLog { - - /** - * 模块 - */ - String title() default ""; - - /** - * 功能 - */ - BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER; - - /** - * 操作人类别 - */ - OperatorTypeEnum operatorType() default OperatorTypeEnum.WEB; - - /** - * 是否保存请求的参数 - */ - boolean isSaveRequestData() default true; - - /** - * 是否保存响应的参数 - */ - boolean isSaveResponseData() default false; -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java deleted file mode 100644 index 75145de..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.agileboot.admin.customize.aop.accessLog; - -import com.agileboot.admin.customize.async.AsyncTaskFactory; -import com.agileboot.infrastructure.thread.ThreadPoolManager; -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.springframework.stereotype.Component; - -/** - * 操作日志记录处理 - * - * @author valarchie - */ -@Aspect -@Component -@Slf4j -public class AccessLogAspect { - - /** - * 处理完请求后执行 - * - * @param joinPoint 切点 - */ - @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") - public void doAfterReturning(JoinPoint joinPoint, AccessLog controllerLog, Object jsonResult) { - handleLog(joinPoint, controllerLog, null, jsonResult); - } - - /** - * 拦截异常操作 - * - * @param joinPoint 切点 - * @param e 异常 - */ - @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") - public void doAfterThrowing(JoinPoint joinPoint, AccessLog controllerLog, Exception e) { - handleLog(joinPoint, controllerLog, e, null); - } - - protected void handleLog(final JoinPoint joinPoint, AccessLog accessLog, final Exception e, Object jsonResult) { - try { - OperationLogModel operationLog = new OperationLogModel(); - operationLog.fillOperatorInfo(); - operationLog.fillRequestInfo(joinPoint, accessLog, jsonResult); - operationLog.fillStatus(e); - operationLog.fillAccessLogInfo(accessLog); - - // 保存数据库 - ThreadPoolManager.execute(AsyncTaskFactory.recordOperationLog(operationLog)); - } catch (Exception exp) { - log.error("写入操作日式失败", exp); - } - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java deleted file mode 100644 index b1d3da2..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.agileboot.admin.customize.aop.accessLog; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.json.JSONUtil; -import com.agileboot.common.utils.ServletHolderUtil; -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.common.enums.common.OperationStatusEnum; -import com.agileboot.common.enums.common.RequestMethodEnum; -import com.agileboot.common.enums.BasicEnumUtil; -import com.agileboot.domain.system.log.db.SysOperationLogEntity; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.springframework.validation.BindingResult; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.HandlerMapping; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Collection; -import java.util.Map; - -/** - * @author valarchie - */ -@Slf4j -public class OperationLogModel extends SysOperationLogEntity { - - public static final int MAX_DATA_LENGTH = 512; - - HttpServletRequest request = ServletHolderUtil.getRequest(); - - public void fillOperatorInfo() { - // 获取当前的用户 - String ip = ServletUtil.getClientIP(request); - setOperatorIp(ip); - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - if (loginUser != null) { - this.setUsername(loginUser.getUsername()); - } - - this.setOperationTime(DateUtil.date()); - } - - - public void fillRequestInfo(final JoinPoint joinPoint, AccessLog accessLog, Object jsonResult) { - this.setRequestUrl(request.getRequestURI()); - // 设置方法名称 - String className = joinPoint.getTarget().getClass().getName(); - String methodName = joinPoint.getSignature().getName(); - String methodFormat = StrUtil.format("{}.{}()", className, methodName); - this.setCalledMethod(methodFormat); - // 设置请求方式 - RequestMethodEnum requestMethodEnum = EnumUtil.fromString(RequestMethodEnum.class, - request.getMethod()); - this.setRequestMethod(requestMethodEnum != null ? requestMethodEnum.getValue() : RequestMethodEnum.UNKNOWN.getValue()); - - - // 是否需要保存request,参数和值 - if (accessLog.isSaveRequestData()) { - // 获取参数的信息,传入到数据库中。 - recordRequestData(joinPoint); - } - // 是否需要保存response,参数和值 - if (accessLog.isSaveResponseData() && jsonResult != null) { - this.setOperationResult(StrUtil.sub(JSONUtil.toJsonStr(jsonResult), 0, MAX_DATA_LENGTH)); - } - } - - - public void fillAccessLogInfo(AccessLog log) { - // 设置action动作 - this.setBusinessType(log.businessType().ordinal()); - // 设置标题 - this.setRequestModule(log.title()); - // 设置操作人类别 - this.setOperatorType(log.operatorType().ordinal()); - } - - - public void fillStatus(Exception e) { - if (e != null) { - this.setStatus(OperationStatusEnum.FAIL.getValue()); - this.setErrorStack(StrUtil.sub(e.getMessage(), 0, MAX_DATA_LENGTH)); - } else { - this.setStatus(OperationStatusEnum.SUCCESS.getValue()); - } - } - - - /** - * 获取请求的参数,放到log中 - * - * @param joinPoint 方法切面 - */ - private void recordRequestData(JoinPoint joinPoint) { - RequestMethodEnum requestMethodEnum = BasicEnumUtil.fromValue(RequestMethodEnum.class, - this.getRequestMethod()); - - if (requestMethodEnum == RequestMethodEnum.GET || requestMethodEnum == RequestMethodEnum.POST) { - String params = argsArrayToString(joinPoint.getArgs()); - this.setOperationParam(StrUtil.sub(params, 0, MAX_DATA_LENGTH)); - } else { - Map paramsMap = (Map) request - .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); - this.setOperationParam(StrUtil.sub(paramsMap.toString(), 0, MAX_DATA_LENGTH)); - } - } - - /** - * 参数拼装 - */ - private String argsArrayToString(Object[] paramsArray) { - StringBuilder params = new StringBuilder(); - if (paramsArray != null) { - for (Object o : paramsArray) { - if (o != null && !isCanNotBeParseToJson(o)) { - try { - Object jsonObj = JSONUtil.parseObj(o); - params.append(jsonObj).append(","); - } catch (Exception e) { - log.info("参数拼接错误", e); - } - } - } - } - return params.toString().trim(); - } - - /** - * 判断是否需要过滤的对象。 - * - * @param o 对象信息。 - * @return 如果是需要过滤的对象,则返回true;否则返回false。 - */ - @SuppressWarnings("rawtypes") - public boolean isCanNotBeParseToJson(final Object o) { - Class clazz = o.getClass(); - if (clazz.isArray()) { - return clazz.getComponentType().isAssignableFrom(MultipartFile.class); - } else if (Collection.class.isAssignableFrom(clazz)) { - Collection collection = (Collection) o; - for (Object value : collection) { - return value instanceof MultipartFile; - } - } else if (Map.class.isAssignableFrom(clazz)) { - Map map = (Map) o; - for (Object value : map.entrySet()) { - Map.Entry entry = (Map.Entry) value; - return entry.getValue() instanceof MultipartFile; - } - } - return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse - || o instanceof BindingResult; - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java deleted file mode 100644 index a5f66c9..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.agileboot.admin.customize.async; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.extra.spring.SpringUtil; -import com.agileboot.common.utils.ServletHolderUtil; -import com.agileboot.common.utils.ip.IpRegionUtil; -import com.agileboot.common.enums.common.LoginStatusEnum; -import com.agileboot.domain.system.log.db.SysLoginInfoEntity; -import com.agileboot.domain.system.log.db.SysOperationLogEntity; -import com.agileboot.domain.system.log.db.SysLoginInfoService; -import com.agileboot.domain.system.log.db.SysOperationLogService; -import eu.bitwalker.useragentutils.UserAgent; -import lombok.extern.slf4j.Slf4j; - -/** - * 异步工厂(产生任务用) - * - * @author ruoyi - */ -@Slf4j -public class AsyncTaskFactory { - - private AsyncTaskFactory() { - } - - /** - * 记录登录信息 - * - * @param username 用户名 - * @param loginStatusEnum 状态 - * @param message 消息 - * @return 任务task - */ - public static Runnable loginInfoTask(final String username, final LoginStatusEnum loginStatusEnum, final String message) { - // 优化一下这个类 - final UserAgent userAgent = UserAgent.parseUserAgentString( - ServletHolderUtil.getRequest().getHeader("User-Agent")); - // 获取客户端浏览器 - final String browser = userAgent.getBrowser() != null ? userAgent.getBrowser().getName() : ""; - final String ip = ServletUtil.getClientIP(ServletHolderUtil.getRequest()); - final String address = IpRegionUtil.getBriefLocationByIp(ip); - // 获取客户端操作系统 - final String os = userAgent.getOperatingSystem() != null ? userAgent.getOperatingSystem().getName() : ""; - - log.info("ip: {}, address: {}, username: {}, loginStatusEnum: {}, message: {}", ip, address, username, - loginStatusEnum, message); - return () -> { - // 封装对象 - SysLoginInfoEntity loginInfo = new SysLoginInfoEntity(); - loginInfo.setUsername(username); - loginInfo.setIpAddress(ip); - loginInfo.setLoginLocation(address); - loginInfo.setBrowser(browser); - loginInfo.setOperationSystem(os); - loginInfo.setMsg(message); - loginInfo.setLoginTime(DateUtil.date()); - loginInfo.setStatus(loginStatusEnum.getValue()); - // 插入数据 - SpringUtil.getBean(SysLoginInfoService.class).save(loginInfo); - }; - } - - /** - * 操作日志记录 - * - * @param operationLog 操作日志信息 - * @return 任务task - */ - public static Runnable recordOperationLog(final SysOperationLogEntity operationLog) { - return () -> { - // 远程查询操作地点 - operationLog.setOperatorLocation(IpRegionUtil.getBriefLocationByIp(operationLog.getOperatorIp())); - SpringUtil.getBean(SysOperationLogService.class).save(operationLog); - }; - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java deleted file mode 100644 index 6fa0d86..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.agileboot.admin.customize.config; - -import com.agileboot.infrastructure.user.AuthenticationUtils; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.admin.customize.service.login.TokenService; -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * token过滤器 验证token有效性 - * 继承OncePerRequestFilter类的话 可以确保只执行filter一次, 避免执行多次 - * @author valarchie - */ -@Component -@Slf4j -@RequiredArgsConstructor -public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { - - private final TokenService tokenService; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws ServletException, IOException { - SystemLoginUser loginUser = tokenService.getLoginUser(request); - if (loginUser != null && AuthenticationUtils.getAuthentication() == null) { - tokenService.refreshToken(loginUser); - // 如果没有将当前登录用户放入到上下文中的话,会认定用户未授权,返回用户未登陆的错误 - putCurrentLoginUserIntoContext(request, loginUser); - - log.debug("request process in jwt token filter. get login user id: {}", loginUser.getUserId()); - } - chain.doFilter(request, response); - } - - - private void putCurrentLoginUserIntoContext(HttpServletRequest request, SystemLoginUser loginUser) { - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginUser, - null, loginUser.getAuthorities()); - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authToken); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java deleted file mode 100644 index c793f58..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.agileboot.admin.customize.config; - -import cn.hutool.json.JSONUtil; -import com.agileboot.admin.customize.service.login.LoginService; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.common.exception.ApiException; -import com.agileboot.common.exception.error.ErrorCode.Client; -import com.agileboot.common.utils.ServletHolderUtil; -import com.agileboot.domain.common.cache.RedisCacheService; -import com.agileboot.admin.customize.async.AsyncTaskFactory; -import com.agileboot.infrastructure.thread.ThreadPoolManager; -import com.agileboot.infrastructure.user.web.SystemLoginUser; -import com.agileboot.admin.customize.service.login.TokenService; -import com.agileboot.common.enums.common.LoginStatusEnum; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.web.filter.CorsFilter; - -/** - * 主要配置登录流程逻辑涉及以下几个类 - * @see UserDetailsServiceImpl#loadUserByUsername 用于登录流程通过用户名加载用户 - * @see this#unauthorizedHandler() 用于用户未授权或登录失败处理 - * @see this#logOutSuccessHandler 用于退出登录成功后的逻辑 - * @see JwtAuthenticationTokenFilter#doFilter token的校验和刷新 - * @see LoginService#login 登录逻辑 - * @author valarchie - */ -@Configuration -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -@RequiredArgsConstructor -public class SecurityConfig { - - private final TokenService tokenService; - - private final RedisCacheService redisCache; - - /** - * token认证过滤器 - */ - private final JwtAuthenticationTokenFilter jwtTokenFilter; - - private final UserDetailsService userDetailsService; - - /** - * 跨域过滤器 - */ - private final CorsFilter corsFilter; - - - /** - * 登录异常处理类 - * 用户未登陆的话 在这个Bean中处理 - */ - @Bean - public AuthenticationEntryPoint unauthorizedHandler() { - return (request, response, exception) -> { - ResponseDTO responseDTO = ResponseDTO.fail( - new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI()) - ); - ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO)); - }; - } - - - /** - * 退出成功处理类 返回成功 - * 在SecurityConfig类当中 定义了/logout 路径对应处理逻辑 - */ - @Bean - public LogoutSuccessHandler logOutSuccessHandler() { - return (request, response, authentication) -> { - SystemLoginUser loginUser = tokenService.getLoginUser(request); - if (loginUser != null) { - String userName = loginUser.getUsername(); - // 删除用户缓存记录 - redisCache.loginUserCache.delete(loginUser.getCachedKey()); - // 记录用户退出日志 - ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask( - userName, LoginStatusEnum.LOGOUT, LoginStatusEnum.LOGOUT.description())); - } - ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(ResponseDTO.ok())); - }; - } - - /** - * 强散列哈希加密实现 - */ - @Bean - public BCryptPasswordEncoder bCryptPasswordEncoder() { - return new BCryptPasswordEncoder(); - } - - - /** - * 鉴权管理类 - * @see UserDetailsServiceImpl#loadUserByUsername - */ - @Bean - public AuthenticationManager authManager(HttpSecurity http) throws Exception { - return http.getSharedObject(AuthenticationManagerBuilder.class) - .userDetailsService(userDetailsService) - .passwordEncoder(bCryptPasswordEncoder()) - .and() - .build(); - } - - - @Bean - public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { - httpSecurity - // CSRF禁用,因为不使用session - .csrf().disable() - // 认证失败处理类 - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler()).and() - // 基于token,所以不需要session - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - // 过滤请求 - .authorizeRequests() - // 对于登录login 注册register 验证码captchaImage 以及公共Api的请求允许匿名访问 - // 注意: 当携带token请求以下这几个接口时 会返回403的错误 - .antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**").anonymous() - .antMatchers("/app/login", "/app/register", "/app/getConfig", "/app/captchaImage").anonymous() - .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", - "/profile/**").permitAll() - // TODO this is danger. - .antMatchers("/swagger-ui.html").anonymous() - .antMatchers("/swagger-resources/**").anonymous() - .antMatchers("/webjars/**").anonymous() - .antMatchers("/*/api-docs","/*/api-docs/swagger-config").anonymous() - .antMatchers("/**/api-docs.yaml" ).anonymous() - .antMatchers("/druid/**").anonymous() - // 除上面外的所有请求全部需要鉴权认证 - .anyRequest().authenticated() - .and() - // 禁用 X-Frame-Options 响应头。下面是具体解释: - // X-Frame-Options 是一个 HTTP 响应头,用于防止网页被嵌入到其他网页的 、