From 5cc31cfbbe6d675d2ff74722ad65544f889a0df7 Mon Sep 17 00:00:00 2001 From: wangxiangshun Date: Thu, 2 Oct 2025 17:19:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 24 + .gitignore copy | 47 + .idea/.gitignore | 8 + .idea/compiler.xml | 19 + .idea/encodings.xml | 19 + .idea/jarRepositories.xml | 25 + .idea/misc.xml | 17 + .idea/vcs.xml | 6 + LICENSE | 20 + README.md | 2 + output.mp3 | Bin 0 -> 55633 bytes pom.xml | 346 +++ sql/table_20250828.sql | 71 + vetti-admin/pom.xml | 106 + .../main/java/com/vetti/RuoYiApplication.java | 23 + .../com/vetti/RuoYiServletInitializer.java | 18 + .../vetti/config/IgnoreWhiteProperties.java | 24 + .../java/com/vetti/filter/AuthFilter.java | 139 ++ .../controller/common/CaptchaController.java | 94 + .../controller/common/CommonController.java | 162 ++ .../controller/monitor/CacheController.java | 121 ++ .../controller/monitor/ServerController.java | 27 + .../monitor/SysLogininforController.java | 82 + .../monitor/SysOperlogController.java | 69 + .../monitor/SysUserOnlineController.java | 83 + .../system/SysConfigController.java | 133 ++ .../controller/system/SysDeptController.java | 132 ++ .../system/SysDictDataController.java | 121 ++ .../system/SysDictTypeController.java | 131 ++ .../system/SysI18nGainController.java | 38 + .../controller/system/SysIndexController.java | 29 + .../controller/system/SysLoginController.java | 131 ++ .../controller/system/SysMenuController.java | 142 ++ .../system/SysNoticeController.java | 91 + .../controller/system/SysPostController.java | 129 ++ .../system/SysProfileController.java | 148 ++ .../system/SysRegisterController.java | 38 + .../controller/system/SysRoleController.java | 262 +++ .../controller/system/SysUserController.java | 266 +++ .../controller/tool/SwaggerController.java | 24 + .../web/controller/tool/TestController.java | 183 ++ .../vetti/web/core/config/SwaggerConfig.java | 124 ++ .../web/service/ISystemI18nGainService.java | 20 + .../impl/SystemI18nGainServiceImpl.java | 48 + .../META-INF/spring-devtools.properties | 1 + .../src/main/resources/application-druid.yml | 153 ++ .../src/main/resources/application.yml | 94 + vetti-admin/src/main/resources/banner.txt | 2 + .../main/resources/i18n/messages.properties | 39 + .../resources/i18n/messages_en_US.properties | 46 + .../resources/i18n/messages_zh_CN.properties | 47 + vetti-admin/src/main/resources/logback.xml | 96 + .../main/resources/mybatis/mybatis-config.xml | 20 + .../META-INF/spring-devtools.properties | 1 + .../target/classes/application-druid.yml | 153 ++ vetti-admin/target/classes/application.yml | 94 + vetti-admin/target/classes/banner.txt | 2 + .../target/classes/i18n/messages.properties | 39 + .../classes/i18n/messages_en_US.properties | 496 +++++ .../classes/i18n/messages_zh_CN.properties | 498 +++++ vetti-admin/target/classes/logback.xml | 96 + .../target/classes/mybatis/mybatis-config.xml | 20 + vetti-common/pom.xml | 171 ++ .../com/vetti/common/ai/ElevenLabsClient.java | 199 ++ .../java/com/vetti/common/ai/FineTuning.java | 23 + .../com/vetti/common/ai/WapiAiClient.java | 159 ++ .../com/vetti/common/ai/WhisperClient.java | 113 + .../java/com/vetti/common/ai/WhisperTest.java | 72 + .../vetti/common/annotation/Anonymous.java | 19 + .../vetti/common/annotation/DataScope.java | 33 + .../vetti/common/annotation/DataSource.java | 28 + .../com/vetti/common/annotation/Excel.java | 197 ++ .../com/vetti/common/annotation/Excels.java | 18 + .../java/com/vetti/common/annotation/Log.java | 51 + .../vetti/common/annotation/RateLimiter.java | 40 + .../vetti/common/annotation/RepeatSubmit.java | 31 + .../vetti/common/annotation/Sensitive.java | 24 + .../common/config/HereMapsProperties.java | 21 + .../vetti/common/config/HttpClientConfig.java | 41 + .../config/InternationalProperties.java | 84 + .../com/vetti/common/config/MinioConfig.java | 46 + .../com/vetti/common/config/RuoYiConfig.java | 122 ++ .../com/vetti/common/config/TwilioConfig.java | 27 + .../config/VerificationEmailConfig.java | 17 + .../serializer/SensitiveJsonSerializer.java | 67 + .../vetti/common/constant/CacheConstants.java | 61 + .../com/vetti/common/constant/Constants.java | 190 ++ .../vetti/common/constant/GenConstants.java | 117 + .../com/vetti/common/constant/HttpStatus.java | 94 + .../vetti/common/constant/LoginConstant.java | 64 + .../common/constant/ScheduleConstants.java | 50 + .../common/constant/SecurityConstants.java | 49 + .../common/constant/SysDictDataConstants.java | 18 + .../vetti/common/constant/TokenConstants.java | 20 + .../vetti/common/constant/UserConstants.java | 81 + .../core/controller/BaseController.java | 221 ++ .../vetti/common/core/domain/AjaxResult.java | 216 ++ .../vetti/common/core/domain/BaseEntity.java | 159 ++ .../common/core/domain/MinioFileInfo.java | 46 + .../java/com/vetti/common/core/domain/R.java | 116 + .../vetti/common/core/domain/TreeEntity.java | 79 + .../vetti/common/core/domain/TreeSelect.java | 93 + .../common/core/domain/dto/SystemUserDto.java | 6 + .../common/core/domain/entity/SysDept.java | 203 ++ .../core/domain/entity/SysDictData.java | 179 ++ .../core/domain/entity/SysDictType.java | 96 + .../common/core/domain/entity/SysMenu.java | 278 +++ .../common/core/domain/entity/SysRole.java | 241 +++ .../common/core/domain/entity/SysUser.java | 352 +++ .../core/domain/model/LoginAppBody.java | 54 + .../domain/model/LoginAppRegisterBody.java | 39 + .../core/domain/model/LoginAppResetBody.java | 38 + .../core/domain/model/LoginAppSendCode.java | 20 + .../common/core/domain/model/LoginBody.java | 69 + .../common/core/domain/model/LoginUser.java | 275 +++ .../core/domain/model/RegisterBody.java | 11 + .../vetti/common/core/page/PageDomain.java | 101 + .../common/core/page/TableAppDataInfo.java | 122 ++ .../vetti/common/core/page/TableDataInfo.java | 85 + .../common/core/page/TablePageDataInfo.java | 71 + .../vetti/common/core/page/TableSupport.java | 56 + .../vetti/common/core/redis/RedisCache.java | 268 +++ .../vetti/common/core/redis/RedisService.java | 364 ++++ .../common/core/service/BaseServiceImpl.java | 99 + .../vetti/common/core/text/CharsetKit.java | 86 + .../com/vetti/common/core/text/Convert.java | 1018 +++++++++ .../vetti/common/core/text/StrFormatter.java | 92 + .../entity/hereMap/HereMapCoordinates.java | 19 + .../entity/hereMap/HereMapLocationDto.java | 38 + .../common/entity/hereMap/HereMapRouteVo.java | 35 + .../entity/hereMap/HereMapTruckRoute.java | 43 + .../BaseHereMapQueryParamVehicle.java | 38 + .../hereMap/queryParam/HereMapQueryParam.java | 22 + .../queryParam/HereMapQueryParamTruck.java | 29 + .../entity/hereMap/route/HereMapAction.java | 43 + .../entity/hereMap/route/HereMapArrival.java | 23 + .../hereMap/route/HereMapDeparture.java | 23 + .../entity/hereMap/route/HereMapLocation.java | 27 + .../entity/hereMap/route/HereMapNotice.java | 27 + .../entity/hereMap/route/HereMapPlace.java | 27 + .../entity/hereMap/route/HereMapRoadInfo.java | 29 + .../entity/hereMap/route/HereMapRoadName.java | 23 + .../hereMap/route/HereMapRoadNumber.java | 27 + .../hereMap/route/HereMapRoadToward.java | 23 + .../entity/hereMap/route/HereMapRoute.java | 33 + .../entity/hereMap/route/HereMapRouteDto.java | 21 + .../hereMap/route/HereMapRouteLabel.java | 27 + .../hereMap/route/HereMapRouteSection.java | 65 + .../entity/hereMap/route/HereMapSignpost.java | 21 + .../hereMap/route/HereMapSignpostLabel.java | 23 + .../entity/hereMap/route/HereMapSummary.java | 31 + .../hereMap/route/HereMapTransport.java | 19 + .../hereMap/route/HereMapTravelSummary.java | 31 + .../route/HereMapTurnByTurnAction.java | 55 + .../hereMap/vehicle/HereMapVehicle.java | 23 + .../hereMap/vehicle/HereMapVehicleTruck.java | 24 + .../verification/BaseTemplateEmail.java | 17 + .../RoutezVerificationCodeTemplate.java | 15 + .../vetti/common/enums/BusinessStatus.java | 20 + .../com/vetti/common/enums/BusinessType.java | 59 + .../vetti/common/enums/DataSourceType.java | 19 + .../vetti/common/enums/DesensitizedType.java | 59 + .../com/vetti/common/enums/FillTypeEnum.java | 30 + .../com/vetti/common/enums/HttpMethod.java | 36 + .../enums/InternationalLangTypeEnum.java | 31 + .../vetti/common/enums/LanguageTypeEnum.java | 41 + .../com/vetti/common/enums/LimitType.java | 20 + .../com/vetti/common/enums/OperatorType.java | 24 + .../enums/SerialNumberBusinessType.java | 31 + .../com/vetti/common/enums/UserStatus.java | 30 + .../common/enums/command/AddressTypeEnum.java | 33 + .../common/enums/command/CarStatusEnum.java | 34 + .../enums/command/LoginPlatformTypeEnum.java | 31 + .../command/OverhaulItemRecordStatusEnum.java | 32 + .../enums/command/OverhaulStatusEnum.java | 31 + .../common/enums/command/StatusEnum.java | 32 + .../common/enums/command/UserNowTypeEnum.java | 31 + .../enums/command/VehicleTypesEnum.java | 33 + .../common/enums/command/WorkStatusEnum.java | 34 + .../enums/hereMap/HereMapAvoidFeatures.java | 29 + .../enums/hereMap/HereMapHazmatType.java | 28 + .../enums/hereMap/HereMapRoutingMode.java | 25 + .../enums/hereMap/HereMapTransportMode.java | 26 + .../common/exception/DemoModeException.java | 15 + .../common/exception/GlobalException.java | 58 + .../common/exception/ServiceException.java | 74 + .../vetti/common/exception/UtilException.java | 26 + .../common/exception/base/BaseException.java | 97 + .../common/exception/file/FileException.java | 19 + .../FileNameLengthLimitExceededException.java | 16 + .../file/FileSizeLimitExceededException.java | 16 + .../exception/file/FileUploadException.java | 61 + .../file/InvalidExtensionException.java | 80 + .../common/exception/job/TaskException.java | 34 + .../exception/user/BlackListException.java | 16 + .../exception/user/CaptchaException.java | 16 + .../user/CaptchaExpireException.java | 16 + .../common/exception/user/UserException.java | 18 + .../user/UserNotExistsException.java | 16 + .../user/UserPasswordNotMatchException.java | 16 + ...UserPasswordRetryLimitExceedException.java | 16 + .../filter/PropertyPreExcludeFilter.java | 24 + .../vetti/common/filter/RepeatableFilter.java | 52 + .../filter/RepeatedlyRequestWrapper.java | 76 + .../com/vetti/common/filter/XssFilter.java | 75 + .../filter/XssHttpServletRequestWrapper.java | 111 + .../vetti/common/handle/JsonTypeHandler.java | 60 + .../common/international/International.java | 26 + .../InternationalResourceGetter.java | 20 + .../InternationalResourceSetter.java | 30 + .../InternationalSerializer.java | 46 + .../security/service/AppTokenService.java | 175 ++ .../hereMap/HereMapGeocoderService.java | 12 + .../hereMap/HereMapRoutingService.java | 10 + .../hereMap/impl/BaseHereMapsService.java | 103 + .../impl/HereMapGeocoderServiceImpl.java | 156 ++ .../impl/HereMapRoutingServiceImpl.java | 175 ++ .../VerificationEmailService.java | 42 + .../impl/VerificationEmailServiceImpl.java | 143 ++ .../java/com/vetti/common/utils/Arith.java | 113 + .../com/vetti/common/utils/DateUtils.java | 220 ++ .../vetti/common/utils/DesensitizedUtil.java | 49 + .../com/vetti/common/utils/DictUtils.java | 239 +++ .../com/vetti/common/utils/ExceptionUtil.java | 39 + .../java/com/vetti/common/utils/I18nUtil.java | 41 + .../java/com/vetti/common/utils/JwtUtils.java | 124 ++ .../java/com/vetti/common/utils/LogUtils.java | 18 + .../com/vetti/common/utils/MessageUtils.java | 50 + .../com/vetti/common/utils/PageUtils.java | 35 + .../com/vetti/common/utils/SecurityUtils.java | 214 ++ .../com/vetti/common/utils/ServletUtils.java | 285 +++ .../com/vetti/common/utils/StringUtils.java | 722 +++++++ .../java/com/vetti/common/utils/Threads.java | 99 + .../vetti/common/utils/UnitConvertUtils.java | 157 ++ .../vetti/common/utils/bean/BeanUtils.java | 110 + .../common/utils/bean/BeanValidators.java | 24 + .../vetti/common/utils/email/EmailUtil.java | 114 + .../common/utils/file/FileTypeUtils.java | 76 + .../common/utils/file/FileUploadUtils.java | 260 +++ .../vetti/common/utils/file/FileUtils.java | 352 +++ .../vetti/common/utils/file/ImageUtils.java | 98 + .../common/utils/file/MimeTypeUtils.java | 59 + .../vetti/common/utils/file/MinioUtil.java | 613 ++++++ .../utils/hereMap/HereMapRoutingService.java | 200 ++ .../utils/hereMap/HereMapsGeocoderUtil.java | 244 +++ .../vetti/common/utils/html/EscapeUtil.java | 167 ++ .../vetti/common/utils/html/HTMLFilter.java | 570 +++++ .../vetti/common/utils/http/HttpHelper.java | 55 + .../vetti/common/utils/http/HttpUtils.java | 293 +++ .../vetti/common/utils/ip/AddressUtils.java | 56 + .../com/vetti/common/utils/ip/IpUtils.java | 382 ++++ .../common/utils/poi/ExcelHandlerAdapter.java | 24 + .../com/vetti/common/utils/poi/ExcelUtil.java | 1893 +++++++++++++++++ .../common/utils/reflect/ReflectUtils.java | 410 ++++ .../com/vetti/common/utils/sign/Base64.java | 291 +++ .../com/vetti/common/utils/sign/Md5Utils.java | 96 + .../common/utils/spring/SpringUtils.java | 164 ++ .../com/vetti/common/utils/sql/SqlUtil.java | 70 + .../com/vetti/common/utils/uuid/IdUtils.java | 49 + .../java/com/vetti/common/utils/uuid/Seq.java | 86 + .../com/vetti/common/utils/uuid/UUID.java | 484 +++++ .../main/java/com/vetti/common/xss/Xss.java | 27 + .../com/vetti/common/xss/XssValidator.java | 39 + vetti-framework/pom.xml | 64 + .../framework/aspectj/DataScopeAspect.java | 184 ++ .../framework/aspectj/DataSourceAspect.java | 72 + .../vetti/framework/aspectj/LogAspect.java | 256 +++ .../framework/aspectj/RateLimiterAspect.java | 89 + .../framework/config/ApplicationConfig.java | 30 + .../vetti/framework/config/CaptchaConfig.java | 83 + .../vetti/framework/config/DruidConfig.java | 126 ++ .../config/FastJson2JsonRedisSerializer.java | 52 + .../vetti/framework/config/FilterConfig.java | 58 + .../vetti/framework/config/I18nConfig.java | 43 + .../framework/config/KaptchaTextCreator.java | 68 + .../vetti/framework/config/MyBatisConfig.java | 132 ++ .../vetti/framework/config/RedisConfig.java | 69 + .../framework/config/ResourcesConfig.java | 72 + .../framework/config/SecurityConfig.java | 139 ++ .../vetti/framework/config/ServerConfig.java | 32 + .../framework/config/ThreadPoolConfig.java | 63 + .../config/properties/DruidProperties.java | 89 + .../properties/PermitAllUrlProperties.java | 73 + .../datasource/DynamicDataSource.java | 26 + .../DynamicDataSourceContextHolder.java | 45 + .../interceptor/RepeatSubmitInterceptor.java | 56 + .../impl/SameUrlDataInterceptor.java | 110 + .../vetti/framework/manager/AsyncManager.java | 55 + .../framework/manager/ShutdownManager.java | 39 + .../manager/factory/AsyncFactory.java | 102 + .../context/AuthenticationContextHolder.java | 28 + .../context/PermissionContextHolder.java | 27 + .../filter/JwtAuthenticationTokenFilter.java | 44 + .../handle/AuthenticationEntryPointImpl.java | 34 + .../handle/LogoutSuccessHandlerImpl.java | 53 + .../vetti/framework/web/domain/Server.java | 240 +++ .../framework/web/domain/server/Cpu.java | 101 + .../framework/web/domain/server/Jvm.java | 130 ++ .../framework/web/domain/server/Mem.java | 61 + .../framework/web/domain/server/Sys.java | 84 + .../framework/web/domain/server/SysFile.java | 114 + .../web/exception/GlobalExceptionHandler.java | 145 ++ .../web/service/PermissionService.java | 159 ++ .../web/service/SysLoginService.java | 181 ++ .../web/service/SysPasswordService.java | 86 + .../web/service/SysPermissionService.java | 88 + .../web/service/SysRegisterService.java | 117 + .../framework/web/service/TokenService.java | 232 ++ .../web/service/UserDetailsServiceImpl.java | 66 + vetti-generator/pom.xml | 40 + .../com/vetti/generator/config/GenConfig.java | 87 + .../generator/controller/GenController.java | 263 +++ .../com/vetti/generator/domain/GenTable.java | 385 ++++ .../generator/domain/GenTableColumn.java | 373 ++++ .../mapper/GenTableColumnMapper.java | 60 + .../generator/mapper/GenTableMapper.java | 91 + .../service/GenTableColumnServiceImpl.java | 68 + .../service/GenTableServiceImpl.java | 531 +++++ .../service/IGenTableColumnService.java | 44 + .../generator/service/IGenTableService.java | 130 ++ .../com/vetti/generator/util/GenUtils.java | 257 +++ .../generator/util/VelocityInitializer.java | 34 + .../vetti/generator/util/VelocityUtils.java | 408 ++++ .../src/main/resources/generator.yml | 12 + .../mapper/generator/GenTableColumnMapper.xml | 127 ++ .../mapper/generator/GenTableMapper.xml | 210 ++ .../main/resources/vm/java/controller.java.vm | 123 ++ .../src/main/resources/vm/java/domain.java.vm | 61 + .../src/main/resources/vm/java/mapper.java.vm | 99 + .../main/resources/vm/java/service.java.vm | 70 + .../resources/vm/java/serviceImpl.java.vm | 179 ++ .../main/resources/vm/java/sub-domain.java.vm | 73 + .../src/main/resources/vm/js/api.js.vm | 44 + .../src/main/resources/vm/sql/sql.vm | 22 + .../main/resources/vm/vue/index-tree.vue.vm | 505 +++++ .../src/main/resources/vm/vue/index.vue.vm | 602 ++++++ .../resources/vm/vue/v3/index-tree.vue.vm | 474 +++++ .../src/main/resources/vm/vue/v3/index.vue.vm | 590 +++++ .../src/main/resources/vm/xml/mapper.xml.vm | 142 ++ vetti-generator/target/classes/generator.yml | 12 + .../mapper/generator/GenTableColumnMapper.xml | 127 ++ .../mapper/generator/GenTableMapper.xml | 210 ++ .../target/classes/vm/java/controller.java.vm | 123 ++ .../target/classes/vm/java/domain.java.vm | 61 + .../target/classes/vm/java/mapper.java.vm | 99 + .../target/classes/vm/java/service.java.vm | 70 + .../classes/vm/java/serviceImpl.java.vm | 179 ++ .../target/classes/vm/java/sub-domain.java.vm | 73 + .../target/classes/vm/js/api.js.vm | 44 + vetti-generator/target/classes/vm/sql/sql.vm | 22 + .../target/classes/vm/vue/index-tree.vue.vm | 505 +++++ .../target/classes/vm/vue/index.vue.vm | 602 ++++++ .../classes/vm/vue/v3/index-tree.vue.vm | 474 +++++ .../target/classes/vm/vue/v3/index.vue.vm | 590 +++++ .../target/classes/vm/xml/mapper.xml.vm | 142 ++ vetti-quartz/pom.xml | 40 + .../vetti/quartz/config/ScheduleConfig.java | 57 + .../quartz/controller/SysJobController.java | 185 ++ .../controller/SysJobLogController.java | 92 + .../java/com/vetti/quartz/domain/SysJob.java | 171 ++ .../com/vetti/quartz/domain/SysJobLog.java | 155 ++ .../vetti/quartz/mapper/SysJobLogMapper.java | 64 + .../com/vetti/quartz/mapper/SysJobMapper.java | 67 + .../quartz/service/ISysJobLogService.java | 56 + .../vetti/quartz/service/ISysJobService.java | 102 + .../service/impl/SysJobLogServiceImpl.java | 87 + .../service/impl/SysJobServiceImpl.java | 261 +++ .../java/com/vetti/quartz/task/RyTask.java | 28 + .../vetti/quartz/util/AbstractQuartzJob.java | 106 + .../java/com/vetti/quartz/util/CronUtils.java | 63 + .../com/vetti/quartz/util/JobInvokeUtil.java | 182 ++ .../QuartzDisallowConcurrentExecution.java | 21 + .../vetti/quartz/util/QuartzJobExecution.java | 19 + .../com/vetti/quartz/util/ScheduleUtils.java | 141 ++ .../mapper/quartz/SysJobLogMapper.xml | 94 + .../resources/mapper/quartz/SysJobMapper.xml | 111 + .../classes/mapper/quartz/SysJobLogMapper.xml | 94 + .../classes/mapper/quartz/SysJobMapper.xml | 111 + vetti-system/pom.xml | 39 + .../com/vetti/system/domain/SysCache.java | 81 + .../com/vetti/system/domain/SysConfig.java | 111 + .../system/domain/SysInternationalInfo.java | 51 + .../vetti/system/domain/SysLogininfor.java | 144 ++ .../com/vetti/system/domain/SysNotice.java | 102 + .../com/vetti/system/domain/SysOperLog.java | 269 +++ .../java/com/vetti/system/domain/SysPost.java | 124 ++ .../com/vetti/system/domain/SysRoleDept.java | 46 + .../com/vetti/system/domain/SysRoleMenu.java | 46 + .../system/domain/SysSerialNumberInfo.java | 45 + .../vetti/system/domain/SysUserOnline.java | 113 + .../com/vetti/system/domain/SysUserPost.java | 46 + .../com/vetti/system/domain/SysUserRole.java | 46 + .../com/vetti/system/domain/vo/MetaVo.java | 108 + .../com/vetti/system/domain/vo/RouterVo.java | 148 ++ .../vetti/system/mapper/SysConfigMapper.java | 76 + .../vetti/system/mapper/SysDeptMapper.java | 118 + .../system/mapper/SysDictDataMapper.java | 95 + .../system/mapper/SysDictTypeMapper.java | 83 + .../mapper/SysInternationalInfoMapper.java | 69 + .../system/mapper/SysLogininforMapper.java | 42 + .../vetti/system/mapper/SysMenuMapper.java | 125 ++ .../vetti/system/mapper/SysNoticeMapper.java | 60 + .../vetti/system/mapper/SysOperLogMapper.java | 48 + .../vetti/system/mapper/SysPostMapper.java | 99 + .../system/mapper/SysRoleDeptMapper.java | 44 + .../vetti/system/mapper/SysRoleMapper.java | 107 + .../system/mapper/SysRoleMenuMapper.java | 44 + .../mapper/SysSerialNumberInfoMapper.java | 69 + .../vetti/system/mapper/SysUserMapper.java | 127 ++ .../system/mapper/SysUserPostMapper.java | 44 + .../system/mapper/SysUserRoleMapper.java | 62 + .../system/service/ISysConfigService.java | 89 + .../vetti/system/service/ISysDeptService.java | 124 ++ .../system/service/ISysDictDataService.java | 60 + .../system/service/ISysDictTypeService.java | 98 + .../service/ISysInternationalInfoService.java | 70 + .../system/service/ISysLogininforService.java | 40 + .../vetti/system/service/ISysMenuService.java | 144 ++ .../system/service/ISysNoticeService.java | 60 + .../system/service/ISysOperLogService.java | 48 + .../vetti/system/service/ISysPostService.java | 99 + .../vetti/system/service/ISysRoleService.java | 173 ++ .../service/ISysSerialNumberInfoService.java | 81 + .../system/service/ISysUserOnlineService.java | 48 + .../vetti/system/service/ISysUserService.java | 206 ++ .../service/impl/SysConfigServiceImpl.java | 232 ++ .../service/impl/SysDeptServiceImpl.java | 338 +++ .../service/impl/SysDictDataServiceImpl.java | 111 + .../service/impl/SysDictTypeServiceImpl.java | 223 ++ .../impl/SysInternationalInfoServiceImpl.java | 118 + .../impl/SysLogininforServiceImpl.java | 65 + .../service/impl/SysMenuServiceImpl.java | 543 +++++ .../service/impl/SysNoticeServiceImpl.java | 92 + .../service/impl/SysOperLogServiceImpl.java | 76 + .../service/impl/SysPostServiceImpl.java | 178 ++ .../service/impl/SysRoleServiceImpl.java | 427 ++++ .../impl/SysSerialNumberInfoServiceImpl.java | 179 ++ .../impl/SysUserOnlineServiceImpl.java | 96 + .../service/impl/SysUserServiceImpl.java | 552 +++++ .../InternationalResourceGetterImpl.java | 87 + .../mapper/system/SysConfigMapper.xml | 117 + .../resources/mapper/system/SysDeptMapper.xml | 159 ++ .../mapper/system/SysDictDataMapper.xml | 124 ++ .../mapper/system/SysDictTypeMapper.xml | 105 + .../system/SysInternationalInfoMapper.xml | 112 + .../mapper/system/SysLogininforMapper.xml | 57 + .../resources/mapper/system/SysMenuMapper.xml | 206 ++ .../mapper/system/SysNoticeMapper.xml | 89 + .../mapper/system/SysOperLogMapper.xml | 87 + .../resources/mapper/system/SysPostMapper.xml | 122 ++ .../mapper/system/SysRoleDeptMapper.xml | 34 + .../resources/mapper/system/SysRoleMapper.xml | 152 ++ .../mapper/system/SysRoleMenuMapper.xml | 34 + .../system/SysSerialNumberInfoMapper.xml | 98 + .../resources/mapper/system/SysUserMapper.xml | 230 ++ .../mapper/system/SysUserPostMapper.xml | 34 + .../mapper/system/SysUserRoleMapper.xml | 44 + .../classes/mapper/system/SysConfigMapper.xml | 117 + .../classes/mapper/system/SysDeptMapper.xml | 159 ++ .../mapper/system/SysDictDataMapper.xml | 124 ++ .../mapper/system/SysDictTypeMapper.xml | 105 + .../system/SysInternationalInfoMapper.xml | 112 + .../mapper/system/SysLogininforMapper.xml | 57 + .../classes/mapper/system/SysMenuMapper.xml | 206 ++ .../classes/mapper/system/SysNoticeMapper.xml | 89 + .../mapper/system/SysOperLogMapper.xml | 87 + .../classes/mapper/system/SysPostMapper.xml | 122 ++ .../mapper/system/SysRoleDeptMapper.xml | 34 + .../classes/mapper/system/SysRoleMapper.xml | 152 ++ .../mapper/system/SysRoleMenuMapper.xml | 34 + .../system/SysSerialNumberInfoMapper.xml | 98 + .../classes/mapper/system/SysUserMapper.xml | 230 ++ .../mapper/system/SysUserPostMapper.xml | 34 + .../mapper/system/SysUserRoleMapper.xml | 44 + 474 files changed, 53553 insertions(+) create mode 100644 .gitignore create mode 100644 .gitignore copy create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 output.mp3 create mode 100644 pom.xml create mode 100644 sql/table_20250828.sql create mode 100644 vetti-admin/pom.xml create mode 100644 vetti-admin/src/main/java/com/vetti/RuoYiApplication.java create mode 100644 vetti-admin/src/main/java/com/vetti/RuoYiServletInitializer.java create mode 100644 vetti-admin/src/main/java/com/vetti/config/IgnoreWhiteProperties.java create mode 100644 vetti-admin/src/main/java/com/vetti/filter/AuthFilter.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/common/CaptchaController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/common/CommonController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/monitor/CacheController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/monitor/ServerController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysLogininforController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysOperlogController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysUserOnlineController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysConfigController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysDeptController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictDataController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictTypeController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysI18nGainController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysIndexController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysMenuController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysNoticeController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysPostController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysProfileController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysRegisterController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysRoleController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/system/SysUserController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/tool/SwaggerController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/controller/tool/TestController.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/core/config/SwaggerConfig.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/service/ISystemI18nGainService.java create mode 100644 vetti-admin/src/main/java/com/vetti/web/service/impl/SystemI18nGainServiceImpl.java create mode 100644 vetti-admin/src/main/resources/META-INF/spring-devtools.properties create mode 100644 vetti-admin/src/main/resources/application-druid.yml create mode 100644 vetti-admin/src/main/resources/application.yml create mode 100644 vetti-admin/src/main/resources/banner.txt create mode 100644 vetti-admin/src/main/resources/i18n/messages.properties create mode 100644 vetti-admin/src/main/resources/i18n/messages_en_US.properties create mode 100644 vetti-admin/src/main/resources/i18n/messages_zh_CN.properties create mode 100644 vetti-admin/src/main/resources/logback.xml create mode 100644 vetti-admin/src/main/resources/mybatis/mybatis-config.xml create mode 100644 vetti-admin/target/classes/META-INF/spring-devtools.properties create mode 100644 vetti-admin/target/classes/application-druid.yml create mode 100644 vetti-admin/target/classes/application.yml create mode 100644 vetti-admin/target/classes/banner.txt create mode 100644 vetti-admin/target/classes/i18n/messages.properties create mode 100644 vetti-admin/target/classes/i18n/messages_en_US.properties create mode 100644 vetti-admin/target/classes/i18n/messages_zh_CN.properties create mode 100644 vetti-admin/target/classes/logback.xml create mode 100644 vetti-admin/target/classes/mybatis/mybatis-config.xml create mode 100644 vetti-common/pom.xml create mode 100644 vetti-common/src/main/java/com/vetti/common/ai/ElevenLabsClient.java create mode 100644 vetti-common/src/main/java/com/vetti/common/ai/FineTuning.java create mode 100644 vetti-common/src/main/java/com/vetti/common/ai/WapiAiClient.java create mode 100644 vetti-common/src/main/java/com/vetti/common/ai/WhisperClient.java create mode 100644 vetti-common/src/main/java/com/vetti/common/ai/WhisperTest.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/Anonymous.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/DataScope.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/DataSource.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/Excel.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/Excels.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/Log.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/RateLimiter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/RepeatSubmit.java create mode 100644 vetti-common/src/main/java/com/vetti/common/annotation/Sensitive.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/HereMapsProperties.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/HttpClientConfig.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/InternationalProperties.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/MinioConfig.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/RuoYiConfig.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/TwilioConfig.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/VerificationEmailConfig.java create mode 100644 vetti-common/src/main/java/com/vetti/common/config/serializer/SensitiveJsonSerializer.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/CacheConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/Constants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/GenConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/HttpStatus.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/LoginConstant.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/ScheduleConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/SecurityConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/SysDictDataConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/TokenConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/constant/UserConstants.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/controller/BaseController.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/AjaxResult.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/BaseEntity.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/MinioFileInfo.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/R.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/TreeEntity.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/TreeSelect.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/dto/SystemUserDto.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDept.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictData.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysMenu.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysRole.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysUser.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppBody.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppRegisterBody.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppResetBody.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppSendCode.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginBody.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginUser.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/domain/model/RegisterBody.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/page/PageDomain.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/page/TableAppDataInfo.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/page/TableDataInfo.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/page/TablePageDataInfo.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/page/TableSupport.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/redis/RedisCache.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/redis/RedisService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/service/BaseServiceImpl.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/text/CharsetKit.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/text/Convert.java create mode 100644 vetti-common/src/main/java/com/vetti/common/core/text/StrFormatter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapCoordinates.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapLocationDto.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapRouteVo.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapTruckRoute.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/BaseHereMapQueryParamVehicle.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParam.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParamTruck.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapAction.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapArrival.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapDeparture.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapLocation.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapNotice.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapPlace.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadInfo.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadName.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadNumber.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadToward.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoute.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteDto.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteLabel.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteSection.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpost.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpostLabel.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSummary.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTransport.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTravelSummary.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTurnByTurnAction.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicle.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicleTruck.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/verification/BaseTemplateEmail.java create mode 100644 vetti-common/src/main/java/com/vetti/common/entity/verification/RoutezVerificationCodeTemplate.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/BusinessStatus.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/BusinessType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/DataSourceType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/DesensitizedType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/FillTypeEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/HttpMethod.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/InternationalLangTypeEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/LanguageTypeEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/LimitType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/OperatorType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/SerialNumberBusinessType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/UserStatus.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/AddressTypeEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/CarStatusEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/LoginPlatformTypeEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulItemRecordStatusEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulStatusEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/StatusEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/UserNowTypeEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/VehicleTypesEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/command/WorkStatusEnum.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapAvoidFeatures.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapHazmatType.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapRoutingMode.java create mode 100644 vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapTransportMode.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/DemoModeException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/GlobalException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/ServiceException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/UtilException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/base/BaseException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/file/FileException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/file/FileNameLengthLimitExceededException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/file/FileSizeLimitExceededException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/file/FileUploadException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/file/InvalidExtensionException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/job/TaskException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/BlackListException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaExpireException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/UserException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/UserNotExistsException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordNotMatchException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordRetryLimitExceedException.java create mode 100644 vetti-common/src/main/java/com/vetti/common/filter/PropertyPreExcludeFilter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/filter/RepeatableFilter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/filter/RepeatedlyRequestWrapper.java create mode 100644 vetti-common/src/main/java/com/vetti/common/filter/XssFilter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/filter/XssHttpServletRequestWrapper.java create mode 100644 vetti-common/src/main/java/com/vetti/common/handle/JsonTypeHandler.java create mode 100644 vetti-common/src/main/java/com/vetti/common/international/International.java create mode 100644 vetti-common/src/main/java/com/vetti/common/international/InternationalResourceGetter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/international/InternationalResourceSetter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/international/InternationalSerializer.java create mode 100644 vetti-common/src/main/java/com/vetti/common/security/service/AppTokenService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapGeocoderService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapRoutingService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/BaseHereMapsService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapGeocoderServiceImpl.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapRoutingServiceImpl.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/verification/VerificationEmailService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/service/verification/impl/VerificationEmailServiceImpl.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/Arith.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/DateUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/DesensitizedUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/DictUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/ExceptionUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/I18nUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/JwtUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/LogUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/MessageUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/PageUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/SecurityUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/ServletUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/StringUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/Threads.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/UnitConvertUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/bean/BeanUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/bean/BeanValidators.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/email/EmailUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/file/FileTypeUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/file/FileUploadUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/file/FileUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/file/ImageUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/file/MimeTypeUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/file/MinioUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapRoutingService.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapsGeocoderUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/html/EscapeUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/html/HTMLFilter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/http/HttpHelper.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/http/HttpUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/ip/AddressUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/ip/IpUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelHandlerAdapter.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/reflect/ReflectUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/sign/Base64.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/sign/Md5Utils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/spring/SpringUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/sql/SqlUtil.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/uuid/IdUtils.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/uuid/Seq.java create mode 100644 vetti-common/src/main/java/com/vetti/common/utils/uuid/UUID.java create mode 100644 vetti-common/src/main/java/com/vetti/common/xss/Xss.java create mode 100644 vetti-common/src/main/java/com/vetti/common/xss/XssValidator.java create mode 100644 vetti-framework/pom.xml create mode 100644 vetti-framework/src/main/java/com/vetti/framework/aspectj/DataScopeAspect.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/aspectj/DataSourceAspect.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/aspectj/LogAspect.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/aspectj/RateLimiterAspect.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/ApplicationConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/CaptchaConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/DruidConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/FastJson2JsonRedisSerializer.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/FilterConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/I18nConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/KaptchaTextCreator.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/MyBatisConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/RedisConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/ResourcesConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/SecurityConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/ServerConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/ThreadPoolConfig.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/properties/DruidProperties.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/config/properties/PermitAllUrlProperties.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSource.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSourceContextHolder.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/interceptor/RepeatSubmitInterceptor.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/interceptor/impl/SameUrlDataInterceptor.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/manager/AsyncManager.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/manager/ShutdownManager.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/manager/factory/AsyncFactory.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/security/context/AuthenticationContextHolder.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/security/context/PermissionContextHolder.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/security/filter/JwtAuthenticationTokenFilter.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/security/handle/LogoutSuccessHandlerImpl.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/domain/Server.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Cpu.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Jvm.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Mem.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Sys.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/domain/server/SysFile.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/exception/GlobalExceptionHandler.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/PermissionService.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/SysPasswordService.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/SysPermissionService.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/SysRegisterService.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/TokenService.java create mode 100644 vetti-framework/src/main/java/com/vetti/framework/web/service/UserDetailsServiceImpl.java create mode 100644 vetti-generator/pom.xml create mode 100644 vetti-generator/src/main/java/com/vetti/generator/config/GenConfig.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/controller/GenController.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/domain/GenTable.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/domain/GenTableColumn.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableColumnMapper.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableMapper.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/service/GenTableColumnServiceImpl.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/service/GenTableServiceImpl.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/service/IGenTableColumnService.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/service/IGenTableService.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/util/GenUtils.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/util/VelocityInitializer.java create mode 100644 vetti-generator/src/main/java/com/vetti/generator/util/VelocityUtils.java create mode 100644 vetti-generator/src/main/resources/generator.yml create mode 100644 vetti-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml create mode 100644 vetti-generator/src/main/resources/mapper/generator/GenTableMapper.xml create mode 100644 vetti-generator/src/main/resources/vm/java/controller.java.vm create mode 100644 vetti-generator/src/main/resources/vm/java/domain.java.vm create mode 100644 vetti-generator/src/main/resources/vm/java/mapper.java.vm create mode 100644 vetti-generator/src/main/resources/vm/java/service.java.vm create mode 100644 vetti-generator/src/main/resources/vm/java/serviceImpl.java.vm create mode 100644 vetti-generator/src/main/resources/vm/java/sub-domain.java.vm create mode 100644 vetti-generator/src/main/resources/vm/js/api.js.vm create mode 100644 vetti-generator/src/main/resources/vm/sql/sql.vm create mode 100644 vetti-generator/src/main/resources/vm/vue/index-tree.vue.vm create mode 100644 vetti-generator/src/main/resources/vm/vue/index.vue.vm create mode 100644 vetti-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm create mode 100644 vetti-generator/src/main/resources/vm/vue/v3/index.vue.vm create mode 100644 vetti-generator/src/main/resources/vm/xml/mapper.xml.vm create mode 100644 vetti-generator/target/classes/generator.yml create mode 100644 vetti-generator/target/classes/mapper/generator/GenTableColumnMapper.xml create mode 100644 vetti-generator/target/classes/mapper/generator/GenTableMapper.xml create mode 100644 vetti-generator/target/classes/vm/java/controller.java.vm create mode 100644 vetti-generator/target/classes/vm/java/domain.java.vm create mode 100644 vetti-generator/target/classes/vm/java/mapper.java.vm create mode 100644 vetti-generator/target/classes/vm/java/service.java.vm create mode 100644 vetti-generator/target/classes/vm/java/serviceImpl.java.vm create mode 100644 vetti-generator/target/classes/vm/java/sub-domain.java.vm create mode 100644 vetti-generator/target/classes/vm/js/api.js.vm create mode 100644 vetti-generator/target/classes/vm/sql/sql.vm create mode 100644 vetti-generator/target/classes/vm/vue/index-tree.vue.vm create mode 100644 vetti-generator/target/classes/vm/vue/index.vue.vm create mode 100644 vetti-generator/target/classes/vm/vue/v3/index-tree.vue.vm create mode 100644 vetti-generator/target/classes/vm/vue/v3/index.vue.vm create mode 100644 vetti-generator/target/classes/vm/xml/mapper.xml.vm create mode 100644 vetti-quartz/pom.xml create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/config/ScheduleConfig.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobController.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobLogController.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJob.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJobLog.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobLogMapper.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobMapper.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobLogService.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobService.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobLogServiceImpl.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobServiceImpl.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/task/RyTask.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/util/AbstractQuartzJob.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/util/CronUtils.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/util/JobInvokeUtil.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzDisallowConcurrentExecution.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzJobExecution.java create mode 100644 vetti-quartz/src/main/java/com/vetti/quartz/util/ScheduleUtils.java create mode 100644 vetti-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml create mode 100644 vetti-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml create mode 100644 vetti-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml create mode 100644 vetti-quartz/target/classes/mapper/quartz/SysJobMapper.xml create mode 100644 vetti-system/pom.xml create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysCache.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysConfig.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysInternationalInfo.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysLogininfor.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysNotice.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysOperLog.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysPost.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysRoleDept.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysRoleMenu.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysSerialNumberInfo.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysUserOnline.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysUserPost.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/SysUserRole.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/vo/MetaVo.java create mode 100644 vetti-system/src/main/java/com/vetti/system/domain/vo/RouterVo.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysConfigMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysDeptMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysDictDataMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysDictTypeMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysInternationalInfoMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysLogininforMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysMenuMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysNoticeMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysOperLogMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysPostMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysRoleDeptMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMenuMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysSerialNumberInfoMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysUserMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysUserPostMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/mapper/SysUserRoleMapper.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysConfigService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysDeptService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysDictDataService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysDictTypeService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysInternationalInfoService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysLogininforService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysMenuService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysNoticeService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysOperLogService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysPostService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysRoleService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysSerialNumberInfoService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysUserOnlineService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/ISysUserService.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysConfigServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysDeptServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysDictDataServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysDictTypeServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysInternationalInfoServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysLogininforServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysMenuServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysNoticeServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysOperLogServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysPostServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysRoleServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysSerialNumberInfoServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysUserOnlineServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/SysUserServiceImpl.java create mode 100644 vetti-system/src/main/java/com/vetti/system/service/impl/international/InternationalResourceGetterImpl.java create mode 100644 vetti-system/src/main/resources/mapper/system/SysConfigMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysDeptMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysDictDataMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysDictTypeMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysInternationalInfoMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysLogininforMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysMenuMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysNoticeMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysOperLogMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysPostMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysRoleMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysSerialNumberInfoMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysUserMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysUserPostMapper.xml create mode 100644 vetti-system/src/main/resources/mapper/system/SysUserRoleMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysConfigMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysDeptMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysDictDataMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysDictTypeMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysInternationalInfoMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysLogininforMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysMenuMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysNoticeMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysOperLogMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysPostMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysRoleDeptMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysRoleMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysRoleMenuMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysSerialNumberInfoMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysUserMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysUserPostMapper.xml create mode 100644 vetti-system/target/classes/mapper/system/SysUserRoleMapper.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..524f096 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* diff --git a/.gitignore copy b/.gitignore copy new file mode 100644 index 0000000..ed8368a --- /dev/null +++ b/.gitignore copy @@ -0,0 +1,47 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..03667d8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..25bc32b --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..c48ef6b --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1068f80 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8564f29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5e9e1fe --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# RouteZ-Service +Backend API services for the Vetti Dashboard Panel diff --git a/output.mp3 b/output.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3cead2d6a03e8f314ffa9e5d368e73cbea63acb1 GIT binary patch literal 55633 zcmYh?Wl&pB*eLMe?iRGT1_|I!>PWe2Ry|!i=msr)4Z?u zx!>Hr<9cd*|KI=#w1)Orj3yz$Kw*)wks*Bm05SJyX|s_fJusWRG&GZ$N)M| z$A~ltiattCYR3~l7Rcm5!DFm6xH^fZ(f`4 z(1xIhG|CwWk2DCHn0taexW{ zTsa3cg#zHF#BKR_z;I;c94Ws*Ao3P-K_cpqyHve}g)=v#OJ=S;n$YbJEY+46HK|iB z3cLYXZ8dk=7abxU?aqFC=M z9sK_+2 z3}M7ar|5pC;xzL)r5z?aZ!#9hQq ze6R^V3O9QH1^Evwei+J#C{R)4R95Od#vH&p5~AIAH~=85-S3CT`4-&OIPB%O9Wm0g zHpH*IC+V4nNkIPXMz#+iC9NRh;?4m3;A=S%K^0Bwza5FdXvG&U7!wr{FFuFkmUMve zHUb#KgeiMRRi5AH{FQ#(Lgf-{$S^;&fA#iuh3_^tsJbW;FE)W^@)Zt*9jK0tt_L#U zR&@!_$Cu3J5*0~$_x9g7lft66C8t=h000m<@mNP{;o`AJB_4!ksRDlg6G8<5$@7iB z4@2Q#>rY@p8Pcn0Xt9EJfL>VCX6f!;7=I^@XNd;6NM_D+V-q|Fnt5jw9s+|PlSUM?M%Rhy##O!R=L2~jv+z8h6!xhkX$;`=MC8| z^{-YpWCdBg4v%-01DA*gPS3=ZYPXs-<&ZvH4Tz&?=U3V+X`EaP<8qE;XzV4ihYiw& z1=R~h44>2NVy9RG|S}cM)Th1}k`J;w?Sm`3reG(ZN`c)M4uWa$i=|;s} zmgWoDZEF+z6^YuL;{R@Dj&XLQ*2&{vG>PR>YZ!2XudtLgexOCqZ!ePl6T%8u5(rR5 z7vT!htF`Hku4@Y*HIo-pYp{boBV?768mp)^9x~}`=HgPbZsp`-DKWPjn(j3wDIqFhqQ3Cb-fLqAdAmkYrz zPl6qASe_02vAd?wFJYrl$^sJCP^FuOga?93NY2V1H?^N6St?n`E zIK7s|OT*+xRWSqJlR%uwog3y>>;qyIRT%?-tQzT73~W1S6EbMb*sz!dG%k61A+JdA z$q_gErTVf(p%$#Y9fD^VBI9f`6d^8>r!r(RO?X!$kPS`(M<~%Q7i(ono1xl0eKcZ@ zu1{?(E){eyRkUs8*sF+tfyn}&B8bbZHqwv#D~iPJQrv0T>Ak2uT{*vY?{DOX{iTCkwb0W$7-bj^#^>uV zrWK{rL_Y+8>(dzN_-nTuHWc<;m<^#I7RgIGoVLw9K0a?u3q`aP(!=&o2m?xZ=_Xe# z!ZG&BdBXIaLtvaxEFr%>Ji4oqXbetgJ=QgXZ31DQHR)DvFPDVck2Sz(Z|j?Y+44%+ zO^kTHo@lIjcsFxS8w*Nb3h&5eMre$Z$W7FJM%zchb^+DBfRq>k<~hkHr+y91{)2w( z#>MhKYyp@_SxETE(kz9Mr=t9zO{s>}!ZaDHJ50@QtM$`e1ue=V%J!yh`s7x{jO?gN zsFyn#rMF2h3j+D%XaJNze3?1K1diX}ay%bl;5cH)1R3v^d`|#dgC0eJIopXe0l^Tg z_x2J_jwxdrxFUF?Kd|LrxbiVk)TDe_am#3+bW6N40(*CxI zW_5cPptO~B`*J@wL2`SKcQb%CGuA@_Cv6RJ7R7fPZC_ab2>}C1REuRX zI@8B-YRUp8I0Tjj-+9qF%8y2grd0OBz{IEQGrYY3u zR^*u*fB(HTlykwt^R;|^lOb|%bbi7P%u$#0Q^+2}j4gf_hp119t*@(*>NvB1FBDaW zRW!NsoInmF1%A7va>=UU{;$!^TrhPIFgo z%h;eaE7?Y_zuVfu{>+`3Ic#9GEj`x?<$NH-26;mKwgMG3)r1U(z3m}iEgd$GMKgT1 zjZ;aU+91MJUrQIq3D+aGhgxRMc1uUZ8wf*m;Gappj;Q5mOP7S0a=@ z!4y<2t|G?67Qs-glcw4wYH7DRt+`Rvd74ph(b#||puX^}ai9D%D{q>2Y6%7daMNG$ zjMs3{6B`QH0`v<3j*hG5O8DVoBUMlnSyx~RC`l=m?z^g&9j%OoTm^#v3E@E(27@5q zz^na^?1!*zP?(C;#^(mC+N~f6n}(V_`=Su$Tp*kH3o($D*hq5G1lcUC8w%>*=vwHumr>XS2U0u9t%<;DoZ`&zy9;*ux znPv(fUvF4HEY`67e18?V7PK9E^6FFCZI9vBG0pPw>)N<;em}#CsPb2ue=>N_$}jO% zlZhi`wdg{mU(oxR{mWOwQftjq6=R~M6hFEAu{s>9g4$D~Q1u9#bZM`;ocv*QpAuA`zY z$D+kFvoI@2H~V7#2CKWYFxtOA7=znosKM9t3*-6^yXJN2zCT4{1R*Y2&x78o)3B>c z{Nox6S9WCVW6gEPB<`zK8P3Od*w0V7-XCvk6yMs12Aj6+6#6>W6cOk8Ca~m129`4o zh9{lA8aPe8{|E~}?tx5P^?(7(#ayuP(PUU~urre3=o$zZ$VhCtgfC24 z*X5;KzcXJ);qWj7%39i9eRC|5T0EQ$OdKUkOCvxf+iP=kF-G&1HPa#QPif<3OXSnQ z!y?0OhZ#Q#X}L0iX+b6%oJoo8BH+E^UbJI6Y%$xEQE~C-)~_NJV%R$ z9fbqLrw$N@qlOh9!o98y4{q0IEZ3z`w4Wx3h`APQd@@U3x1}rN<|)T=(E2At5u|=@ ztnl}wFl$oH6jEAU#tQ12%s!XXe)sm0eJqsPa(Sy@&nY~aTS7e5cLQSP#NYI2H#4AO z-lEiM=Q$K;+6k&RO=SEAL=B6hl00d2s9LAUxk<6KkD&M}#%_4NF#Xuw^6SjiXgf1G zhy$)qy5);mndb;98Lky7y?)=1R9_j>AY?FO#6z)QJenq4<0F1m&fQnx6oXf3(n@0U zuMs0h=_50%CYdNpHeXp;Sxk$rvECk&!ixr$ht%GWHuO?fwSp-i1}a#=$aJ~zE-skY zVPQ7mPe#=xzxVLy@I`JE$z?e;bS2t?yQ8mg^L;e@{_gT3qoWNWV=UdTB?nn=B;)1p zk7&q>0dRTfFuTLx@Sc2{J_)tiZ70=4xqv=SD>7KuTWh(p^-KZZQp|N7){L#**q;dN z2_}8P!3?vXVpq5%krSx2R+rx1y=@IH*2T4-tSnOUxZ_$fibx;J{FH1!jUXZvS?sCn zW5%M8z2D`Oic>=f+IG>z%pRM@8u!e^r8-P4)Jle$!Np01Il}$p&g14RbFFqf|~(%|=_4%DatVDlC^CBaQH zhK1OA)MMt(>?idH>;1Ai5$89)v>rF3Q-`;k=@_cbJ zetR2QPG&;z8T~A(GcVg$o&3a&^dngZlfLcySB9)S?=^gJ(sha0uOG=ooi7vi)C|Yd zDCT*#dcv^;pAl#IbR}lXVR+9 z=x}fk^UqHUx(L;6$AbqHt(;mcg9oK&(pO^G^hG}mB6rtc!~1awSrp%1-n>0PxYWu}w)A zJ#O(Mg*y9&fvze_V2xm`Az(tj`0ymBpcU^gp^5u$|A~9~x1`muQ1^2f=)k((61M}@ z5gdTWQe@S*Or9D=9%ln0ovwJ0K5$+F;*M}j<_!Oog?8O)2}?(*)|?1SnB%+Uom6xs zBH4OYyAA2Oe0w8)%vfwO&iZW6X^cCvSnz2%OAt$|Bv>TDU>rCqu8DjV(F+Kv7Yo^s zTz;6#8gH_FPW2XpLzi}VhdRJd3asE8*|0wBuv&CS8@3Y(OJQqwLsCN5Lo*Hg_X>JJpJ) zOun&7+dorzh?f@E_}#P%q1gR637#}xjI7bml(ScI`opl?p8j6+vlH<5;XwQNz0fY` z6ydp7k!7Sw-DTbwC8Mf>Ukc5Y@mZHA)2{Y5)$q_cUsbP7vts%_G66mxMZDF^dspE_ z<)L$&%rs5r8{f_j%bQG%`URRd9(6~14CqWrDNJWy2Obs%8k^l}Lg_U)9}0_;lhVF_ zsb$kKEV|dIk4c*nCr^JiHJ`$Bq#@HWv7C-fNi3Iy3v7`KZHYB>X?(K`9%JxB;`Fl> z5)z7|X_RIF>j@I-yGy9~0ebVI)tOq614yCqxcHG~x{R%J3ngU9hLA06WY_RWR8%Qy z9RrRcMXTJGg)g{V(t8ey58d7dXC=Ih1?kC4t*PDj*Z>Hu>8iEDk=0O1z++pO3|c9z z{>fN=j1OFt&>129KD#G9R@#uE&47|7sckO=0xrW(aV1s(JEOy7JlND#RsIv|4ZzQF zkpp!S1Ul1}Z8wu_v&kntZ_+0FJdZ1A7)~KXRAY+1x}kW&;2LM@jo6b{g?!6S`rsYt z*Oom(k7%Hq)6Ok0-S6J#3^f4#NK4R~n7V_v@98pAhCMSsGaV%Bk*!ptWp-{zJ1Iy^ zG=60IzW9ds4Lpj?!V?LnRj6Hwb{I(8Pbo z4u|5C@19&)EY0$53D*()`+Mw~x26vaEIkY|kdo81UI1cBW|ZN2#Vl&9R6Zzwk|FHN zj6+j=fm4Z{F&CX6B5Kkk{(hq4|Sis=|aHAYX{i$^4a8!@hRbg=4375M`50i(_gOJNNSAasCVsEyT6sb=`HY{`+Of5WS$a3 z17kPMtP+p&a~tsgEW`Oe@ICk?1O@>L07SzN2)I1=1sD2>NSYVIB5GA=)6OLmv21l3 zTR%BwABeX)MUcRxj&driWd!np9pjLcp*GtFm;hdqt)$pfPI z2J*WO%zIKNpoGhZ+3ub`zVRGWFKX^56Gn7~Y0~t?JALd63TA_4t+yqWVG_pU=6-k3 z7@S>KkPghRc~q16T$rt~b2Pvn^5^|3IBXSc_jmm5r*2gFXM)jj4R#j|Eq( zB~#yo#N=EjbD&SF2g{@SzOij(QgIG`$@{n%F{v_q_o3|FC;QX4>)UoG_LXW>RO*NN z{DrU0yxa3%d+xH{pEOMt53vgMrpQ)+SMnYhdfd`?qkcR8?(cC`fdP>9P=diQpCe#3 z@)gJL8caN^OeiB*RX>hpRPtHw_5jsfTk})`FVPVIFm}tGRN$W^16a7xLzMs4&^a{y zGE9KY^l+S_wj^Le_>V{Y6G)2GFdBf17q+g6ZiK~pFRqQXQk~o3|HFVR1v#XDYfIm( z>{_C+K&OGE@J6yzzdWT}&{Q1};GMuoMOPvU-9JRHC)z&B;5P`)Y0+0R!WA)U|7-{D z)VO(hSv*;VbGd=i-rT`Po>8R7(neQEKXA6uzoQyHW=q!3MDEGBEGK!`E)Jm60opW6 z%>rP6ln@Obpq4y|>yHxmU;u2YWsv)-8*5qM9!BD0xhO_0J9fd2xCzJntL-=LB)%w@ zXMC>Y2t;K5AIMZL!Y1F>b$Ei{w5lzFBkjBaFRp6rW8*ds97J4Vg(o7W-lzyB@6}mS z{X0TCLY&;ZFZ_^Y*gCpg-(Y$(|?+|?!Vd(-e+n3vTC*pxg_C! zO3k%p&2-ZHxfJtVQ*4!)Gq#Fn-d)6-kFz#VAbXuFJrN!i78-!FZj$FH>6=bS02jb% z0_+FF_W=0e#nO;0CspR&TX)CJ={70FWK{j*{keecGjwWnPSgQann`Tc5ihf17s@1=d5s&|IIaW3~y=r@4Hk(i8l1Ax9LcDTkD z4*+NQZD=9x4s4}tLJ84-X-3x(FlBq*ZCIt~`ZfHasC3gft!BZ?H{%xMJ9yM81>v6v z83s{WC`td33@96>l}~6?;raH+>!ULuVp&`Zv{EE?H7$AkN|O4s{4_e3-ArGxgMF>% zx#glG{_Q)5O%W2@5Y^`tbEDKE5#oJsg&`fsnybn6g4Vmt8n>q#k~Y~thd?t#uI7=b&lQeCqY{u(0bUXQhH+W|n)_GZwAO;W#EGaen6e^(mK+N5Vd* z)3wWk{!PSaTro6yjf?a4*;YT{iy2^vSZeREtYOi|3z^BS6!VLzz4qfa*nSN0rx&qD)bmB#7?G zYG1L2ZK3#_)n)7Qe^;ihv)GmSer4L;6rQS6meg2*;x+>W{ z%ldR2o?+KUozBLge=*{8=Le~ED8>O7esmd0Dy*o2JNy8gNpr+mZR*<4BP&3%LeIb8 z7uUcGvpIlA>L>7qB%C?q)!;+)m#UF(^09?vCgDTbbJd^L=OjAsvv+^ZJ_P$S;e9wa z_h(}-QExgHw&#hrakn+D2cmMb?xMj9tiE%<2(j6bB)3fMmQGvTVq5HR2Uo8nK!myg$F9EV>!4CPq zIm_}|lSitc*wUoZy$HyYgtSVEU}CIPY&a^Fh#%4cFdz~+Ai)hFE>gIlMdQO%j{>l%DN!r5|6EcAhG!6^M93O{uw%UA`=s-DCx9SU z3mPnIMnPdyEsfA+aAyW$(pJc!$vGSLIub{*Aa~3R2KDGproOgf+nKZyuZtv06&ejm z2v3Aw%0~hKd=3Cx++g1nkph&E1^ev2Bq8Uyx;Ah)|D`;g9xLWA{Q?uaK(lgt+xbT`<%`n%{B5P;sdO+(dhA>RntANk z;Lwqn4>Y9ngdvGHHmSS8HSCy9xt-Ztj^-cdgb_)_946~NZVD}{d^AokCoo7TGb_-u zw!{!_yNMDGuI3Q0`wF0tmgr}7tp6X74q+Mf1|>NB+$$W-ujZl#quDEq`iSdw?w0Kg>uh` z0zq7a2FSakiWN_pHRKZ7swS@FqbR~uL>^g7U=)HOU1#MtrsQ(y(%od3yYBF1q5MgR z-C6}c?_dP;MwInf5ryQ}{Urci2?7AsmM)tNk=4u14mNJ~x~mv8OQCt1&v?6^j~dcq zmRLd=w^iWfqT87@h7&|oMXNXl2P~IL+6PZRH7sPh=rJ zy^W)aP(Y&mUz@_0pL~WnUGYZKG~8mNa@xF`ricO`CiT+&rIBc82WgZn8tbTzY3d*& zWgcx*4T>KZ>3M{)!kukN7k4{un>O6{BsJ?rVCYb-alvfX>aGP{ettcO5J2TFC?*5g z8lt&J84@6#!w>W7YCbb`E+R(mWeRq8X)P&ZMBeWGVcV)8QdriXLSXMm0&`k_j}Y3Q zzp8R?mx&N1=bAb*-!ygq_3d$-#Nb!|Z0RSMoNE7v^44s!kSbhTfpF@;K ztdq{D>%hE43Kr59Ogr2_7cpaLDH4M!%}AYuG2=h$u_DwVKkF~# zE;hf=CLBlVGsagFbSCKEuP(ZFH%|qG%;A4eJmN_HTTMu2@K&tmN{3nqJ~>vy7y$tA z;qOi55MWG$aKlKfW>CxnSp7`2bH$|W5YjA5dNEmRp~ss&@v4@QL8g0W0bJw=wIlNt zu&eF}ZF^IF)2%5R9WUBmZ!d3%`uS6sF8_q~pvkhH>JHyE$JID(sj5g?MH?<#3O7Vk z(c8jMQ_?v3Av)|9{Cff*#?cPDwfmC+9<_-uj%qAOXu$WNu;UXxgcvLk!q%X6wWRy2 zXF_R`w-(EeHuhImULFNS4bRDkx3dxJUx$PFMEaPyIvO8s$Ue{5mq zkb+kvgC?!IDsGUGJ5;YGBskF^J!$dn5)R8XN2^}qZS6&zPljFe$I$GN{G_C}@4KQ1 zSKs{NWhY|!2AxY6V+-|&gq{e|XyQmJJ2LoIB}MFEv>*FQX!w@CmUfnXs3#x=3HPpX zL&G)4g4_VNug|_OZ-RyXRMD&euH@MrEE!}!4)k8i^23X^Di9XiJWfu=-6v}T*Z}|l zbv8TCG?tt=LNzfS5SoMl(i=N>)wM4sv7bqcl>Vci`@>fKn0uRS&BG`-P&fml@Op{RWO!q69r{ zt*3;CEHRMP{0#Nu9b!h{s7bX^U9v?~1K1dGDm@>1pH>>H8160bo zBDR?E+f$ZN%OW-Pnxw`COT!MMPQ(lM3ug?zfLXiLVXA=%yu#c&_tVLmQwS|=qpgaH3JOiHBrZCOm;~<8a|$5Lx6L}0HkG$fumR2W z&@u%;2mpZ1J`2GR!HB~k3xwzC2AZ@YqZv%yh#m_3!i%7bRqe(mKpBD;hkmDGnyYfo z&rw9wUC*Dy-;?2Q|3x#|w$q=8-{M*QpAcZ#BTWdSlORgGkydPxl<_?F-f)YuLMV^>@-T#tlTcpS zw%G#!BVa?30F>~6004kcoV$!{eKZ!{QgC-IFgQ8{HI9q!N4(y{j|xiefGyM6-rkVl zoN{Ss{+Y9BCD3IXV?ss3A$_Jo5HkkXT+d=2&2Rh=HJck2FdjW47Qded&Y$R?zf}sm z{F7=05gtq8E*#7Q=TXwub6P}k8`&kI|N0?~7z%8(LZ`|mpg;@46plT>_F_EZ7W-Hu zPnYWRDu!r%XEXgmcFCB%>uEI|=I^fP?s$%7%5UXD8HHOwXm z`8JmBF2Z9Z8}fgnk^kb5r|0sC&yW3||M$~ptHpPliyL+n0Km~4-J26<1{Gd4Nb4=UF()4%XY&<0QwQaz%Kec=vM z<3RsluQDfp{_|^1OvIXDe|MZEQ2Ln7TG>1~t;s0PHAgwe?h&Q))qc&&^Wg>n7f7IF zF9DZJ4%aVhw!A)Wjt{Dt%-cSk+0zvb?)g_@#kzZ5_8c% zbXR8OqXwuQi{f)u;NQWv(Kta1Eb<%CxyuVB13mC4g)k{&ySe<{{IJ8OFFkI577dyM z{Ca=AEC9IIDFM{~mQZ;>@;-xf=ihKBnT3h9w4^RB)Fx&KeOHXLgof7US4Sn#%5?=l zkj9+J;ueGDCXacsm)7wN2CD+YJ6&r*Qr!>P4hws4fzp$-YvOT|NnSeYdm4Z^Dzoz>VwnjlVT zJdC_fZCaDo7G>UbzN+a@>@*TQf3uqw;CY>-EO@n|1y$z7U!qTd4}2{c*>rtdXrRZ*MyBiMRbEg9ny!uPy7 zT|x)kCVCFTmDfGFKP`o+;}fC|#vlolu~>O}o8Sz#2gT zn(CKuz<5qm6TAahdv-Vz=ypq8_4}(7`T`b(w^? z2xvs2Gk^Kt-l??z%0r{j6tRzr4&Ob-63WYG(qh{({KB%1xHsQ6*&8cyQYY?wpRM`T z!AwdjebjJGLIhLi&N@07N&~tFBb)ZJFjFwhKzgpZR%O|-$YBl^2_2jR`4p`5$#9Ls zGo2}%m7R|Z`2w1=o%{KxteRe%pDj&WCtgQoK7C+p#7$Jz97lw#876*EiH8(M+ay_U6=6D+e)JTwp*OLK%cVhJX2T`1AlR~K2M9P0^o99a zV}9WMTRvura5kiCNvzW<)6?K%8iFnwniq8f%lFfFP~X7x^kmew3_8D_o^WlpF`j%k z+6Z--b2@eeU*{P3+ICE5n<#1r0SOJac#bVF;DoBRb);uf)~likcrugWYi_=^7e9K2 zohgt~q+5G6wag3hHnfMkNx4;*`SC05fX@Hma9SWl2b?fCL!d|m1?)h)F`avL7)j)n zy21Tmrz55fVhtlhq^rF}lCpL|y94z#;x@9UkGsT#`fnchgDoA3pfyaTg zb@&kcGk-=1EN{cVHFN}BB5;yL|GpFiV*h(ksILv(4_6@tsdJUn*ui_&DKBumT42&6 zlwj^!-A68n(;?P-;JIooy0qrYS;B;7ixS2S2vB;qB&9+YP>;t#OAQIek(dU2wlykT zwA*v>3@TFT*Qw3OWc21;k9TMvk2P2~*qJ)o91qxi^%Bc{?9=drmhOQ#w+r^)0(IMKW@19QOFfTQ)-AjQ{qZ*UALMCq(0)Qh%Jc z?JJw#WXPmEZEUhR`k+oDk@27$Nc1IY>Et$HJ90 zPbW}297VHEl3#8-nRjd#_(RT;tN-SPy@HqHU^Sn&jGDCV+UI{bW|QQOB7R-wES!L5c-dJ?q#~9SeiIG8oTwW+NA}rS&0WPQjjE>9z+#8N$%Pz zfp`tt>=`?|HGD7Gg9b=fgvu}+bI(^?^Yl(?rGiQ%=nCe*>{U`NNExmu zE5L$`@Hp90ZM;-zJLTvGX>%dBxZ|Y7@HI7)l`c_+hl4}Gfp(!-nwf+(ws>bILgzCF zTq$(9g8vD%csVnJgTYC*g2=K7;6^2v;)bd+TOe$p4TAF-v1f*)@w5m~ErjT(eAHe| zMQu6_;*L_*oE3$-2^gyNH%&~|@;(~&Ep&Mx4eoudS4e?(T5n!}0RZk`C`)IXO5GP< zeH|VLhE-TB#E_id2lM0Z{rCeX6Q7}B6ysWOM^A^7Z)vAGzx54wckn{e*F`}GEQ~GVVBs}%%zHt&oo@-pB74MI$@mV0wlb}_0Yh3BwgsYBe9{{z#^QhlG5|7 zKdcf=8 zS8#Y=Tm3XdrCvxgMdg_nrMW_b#d+UWy6n;c5|vX_h$W>OGV+Y^p=UE|d^~!uwP9;i z6qlDA&3hWo>s?r~u4~`m&u{ppe#!2%>h@%ur`o@MW}o`NZf8j}1OUjVG(?|&BH0ck zt4@}Ml0D~Ne}at_r}XN5c^MvQ#0<8!;q)rIwM;v&=F zX%;;$WwI@9@j?f!KmUZz0++nYfh8jG`Vs=BnqhTba)WHT>+G3w!(}R5@)g;<$FcQb zNQNfjZ;k6FPS1*8qE@Lqy1#7QK#063tm&V;c2TdaJkJt#1}oLeHQOh3Qk{g;su+LR zw%dkeX;dyJ=dyWN{n06L*_lKGhFEzeO-2n|_iXU!TX`Y{O?qh!F&%dWYEJ?H5CAMp zF0Hb<8$LBB9J5RlFJu?0AB#PuQ7S|LSIgLD1Tu9mJK^6a9BlY@o--2Ll=WflmEc16h%YVAyolGTDv2jFU9p1* z*^Gj46)9FKZ!35i%eR;;)XHjsDB0Nr$lC0&@2Md=i^aAX}y;Ab`n z$y+hLGRZpxv(l5<*NU?3DFt3%;-$4Y>Sc9TJAN6?^rV?fx8zessZ>LXS{W7{;T>5a zJkC!KUa0upA7s1IB=ql2{lC%ia`Eo}D;<_om)|!myv%*;|64nRtQ+t+1^yE%1TeUg z%C}VmP;*#LYwCXk(#OKcdinONlKan#*mQOGpoK`t);Yc8gpK9Z!oE$0ndtE!vxod) zZ4x%$r+gEZJx0(8i%+V{5wB5VMmxpDf8}oYsUN+Mxja$kN!T*fjUcCz#h z+Xb;Pe4-}GCRywJqBt{tmrDyS*{hzXS)gxxx&1asW`47a#&I>t>*wub;?F0&+uOHy zcqQ(2rmQAZJ$ij`_iX(D#E&zpwqxsh1(#f(f^eh=R>zD4@arDa!`8s~J?V(deO>eL z^dRD0!;sFqxMB2KDa(im1Emt8;uoMVwG30XQiKI|Xey$Vz%Xvh-z=^e%8*?`B+4SL zN(`l!c!IzlVlW36p&|}vhcS>3UY9Ngv}AIl)tuj%<2N@KCCZyf>Z(r7g@^a0FZDUF z;7PaPR9c?n)t-bmg~!8893BDyTLmZk4DfFvA5r0k>Y7TY(%7_qsHe3efhZzA^t>-+ z7FGr(rPFY#OLWM;Qy`B$Qlhp*ihye$?5{;+DHj0?k(mq7r~OdYJT|exocSl@1xP9< z)v6NVLiOQdcD^HF6qOYqn+G$#stN8Wqq@>2Y7})5Yq?G0FD4AkWO{sLtxpLZ=Sl+| z4Q)Aq_9x4t&#lDZPc7Iy?S6|qD2)aIrLH9GY{qH)4$lZ2a8iL)Qlt0P&#XQ{$46Tq z-xk`6z?Pk1t1{W==I&v^fSOu=8`nH)w_%mrB#)+HuCQ&74fM&QoLMmCC=;}BU`3XV zR{mPj0zQoqW>S+1OPCvPU>a`gZxzEvgaL{dwnB^eg&6~HuZm+!+~eV?p&P^t^s@-j z)Jy;ZI8|wA6&kC6ZbL;0LUvjK$9q-V^3EKY?-c^tc74loQ_X`G+=0~&91%E28S?xa zI@1*&j4Xy?gNmeqoSL$s0zNJgdhZhC3JQRMHtwbX6`yy3MI!Lqld)=6Sx?3H)N1J~ zbaT8;$@=r{LqH|awC`t+;i|>a&l~zAJH8DAjM=qssirqJD~NSxaR4a%=s%%6XfjWl zDhTW~_OX^}zcl~ocQ7Z^4mtR$jRPjXRt;d{Tt?Y9hHC7=NZU*LzfH)-v3N?K2IkUC>n z;N>XCj&zV6hob#*tszHYgBn>CtMBW)CY$@mt6Yl}!9XL?=k_7|O*u0qK~>dYG^p$( znNCW_%|N#UX#oZT0aTSTI;3ZyV|oIAv&o2Z$T;^0Idj1G^xyy~^FE;>S0wnRB76cJ z2GMtzd1q{7I5po_`a0N9ME%}tkL0{P#cNAeADr^a4>SwhX_7A$OeZL}ja^J&#uvy<0XiSxrZn zIAl(^rV~qDvz;=fm5m@-WW|yzJq`JGNp9Uffw@zPr(l#(tXzG>IxiLXQL8qS>r{eq z#ZIm^x3y!X=A`qm`Zis_Jci6hf%T=O2cC_Ug3jM8(_ z)t3F4)s<`-cG3+}!}JnI^!#u-x3gJB2G_ou68gG1p%3jZ0`B|q1*z-m_^VA|Yml1R zLlpZVn6K)MCvFkHaub<_=Z%&%@=a8zigkl{r=Y_O|&A6hKEBM4V}Q#2%*Wra}lW;5-vKO8SO{ zCkm2lp}mrPWMHu=ro8Fb>VG?>K&am2Z|QRbWBSx%N2i$|se#52S5@bdSb<6&Y$`-la= z;*G_`>9|Bx9Q#R}q~HOL7P06JXGg0?JIM>k*$*h{s5W|qfv3j-`#|stS(P3V`UU-i z#b*@Ma!_5@O7_Ee&rwkR`J%X7Fb47k-AGx{z7BQyBJn3M@L+EMDS_OVgM0wkchMh7T=q{V#e)s&|T4A zDMm3801JQtL%+YgPZAQI{;dGMjPHdJ@1b=@AC;cP7bn6@sbc7JWb=~75lN3~aF zi6bxd>YBu{2q*0B6;BZ-Ew+|q`_Wti_nQc<&>}gzKt}Ez1@jI8EG^MLp|e0@fe=p2 z{nNgwy3+C?`lHpMPCuWqM*-gs#NHn=YdTRRTg@dHqv%C_2=Wq2pR`h&%~7cN2k1mU z)rP5aAQ4Gf@Y+KXbAP|{L~Wx&^+X8Ol zbT0Whdm%(B;b8d8hI`sbA@aK{@DQP2Yw?vH8vDU_pAs2{o9oO3tQOQG29~5c=BE|n&@9$|Hzp*XJOnX3+P=(->sUgze>OT?Zbb9rnzYy|YC@{Qf} zH~b@X4@Hcc#Dn%OXaqFd(RP#zI3KHNcj+e}t9{u#c9*A5G+&Z{QqhEu#J)u1oCLP= z&&sR^>LyEC#Ihjgxc!!EX&TUxNWAHHx)m-xi9Wdxjbq$jXA-uVxH0<%nRQ-sffvS< z!I@c88n%!2;hTLhK=093Qstm#uJ8yt*IoN8F#5pRE=~9SX$|7OBN-wS#x=tqE{Q<$ zNj$b8G9Cr6rbRz6hRx9DOO?LRO+wEqUzWc9RX?l*@0bdGF%82G(H|BWDzj+L(+uI% z^0lF;vo}iKv7zDe_vuZ%XoSe#slh4{UN%H-+90|MeYY@cSj#Yt0Z0DObYn)-1F8)) zlmFRiNP=_JxY|TOH2W(!*$C5RL?(1WYF!ruKVqjS^E1$*jz5il;v%*kRAFz!p840H z_p29%HG}%knQD=58(1o67w;P4)r7Dr4t^fU$k zod;H8P;;tDd1~fo?@XiBbuy1@*^?rNb$wHP-_F-m`arE8FeD<(mQd#66mIj$@f8TE zpw^W>om#p5l-c6D!VdBXA-+-3Ze|kkA2yF5U<8YPdlaA zxiXlByuO)6_P-Tuzszb6!MV7T?0dDkK?Oj;m`ZwNgB=X`3DsV*G8j!qd*=!fGDwtv zckw85eP-Ue_drN@@NiKGDRqiWTBig&dX|6XT(FNR74&O-T%4n7VUViNxM&K;idd*R z*E;2Bk*!hvGo`MpToQlYcE`{o)l)^=36z8~EMVd=yO!6p*OTPON9I&3#+Cqx-% zWuXCLnD{%HNHq=*8w}2m#fq3qL?dC0U%obsh9?)4?s7^cztK*~+J2*yt1FLscho^})ZdFfyU4-I8lF%(-;4d4j#gCGv z)r>EoFXdWg>$vBc)=(ewDcLsZat3QfJI>1&w)wC&W7i`zlf=zN0&umawO#K<&tzDa zogb-e@cDx6lGkxjkqUVzPU+%uT#LY3REL`I1jF3X&_VpBi9rI^cymtPZ$&}{X+M+nsVV23 zSJQ)Zt?bF-)5L)M8@NxBPi(ktkk#$JWdLAT0xq0%BPJ~gkvRbm1|~xG6hJkJXD)3% z1&1MSUNRry`>A@$!40NrQg}UkuitOk$Nm|Qn~`b*VTU-B6G>wa=Rs-EeWWMH_g4(s zJl{V;KSOo(oh9q~xQ6BHPH<;91+X^W7S(^ui1a4a@D|OZ+{GKh?->D30$~A#f`j#x zApuTBdyQJ_yEL*2W?49lgjg}+*=Y%|JFKMiNHXQKC27yMCfDfNuXwE`wvq^@zxNr)NM% z1P`nJ`NjFeTc&`|vh#M~k;3n5FTx%I`MDyZ3mA~$#2cQYy*UbZ$et)`qXD)q8#7uY zvZ@i;;H~<{QYM3Q8I2Te?C5Q`+7FF?KO7k1T3S=BvT*l+oXHw{yFZW6Ru$=kmS@;a zP5Y&fo8L_0)qh=f1>U9EcuEA^q<6(U^nA(yNCI|CQmkglIf}3cf%LR2oR-N*pvGY; z3b0xXR-!u}8fjRe5~KQRgGm{SB2jVYo)AwVf%&x`m0K+Q7ge?}N7;&_w#(q3pWmKe z-(LUQ+x`hfpqwp(+Q%`?)L5C)%PH`qY4)Yds335gXN_mj3{;v}B2UYexw;x?;-bvM zV;Th%n78ug6lL)U0l)UDdI^3k1nGNmx(iZ|;Su0f|CBQ+!H)`hI*;M~E~br4;gBfU zB9Jp58yK+pOXmyG%;wdm>(eC;zB4fUD@}wzqq#WJyN}K@42zK#D+(DtG&C9}4P@sG zL&e@UK!YVnR9A)ax1Y!iomg)uaVe!OTlDeZ$i0_Fil8%>ix9zOM=9>}9ai>T?g{o8 zEY*QWN850MflKx_PbF(VsWJ@4kb(Kz-Y zs_Jpydf-tOpM!nx2%@Scn^BJayKOZlL$hsHio@Dc!+XqaQbG3!ahfE*eNsIejnrxZ z20rDSL$ZA^Xu3=GH8>EaT4nT~hY9I3$KFoYXAW*F6HJv5uuc42h}UXP9A@bFmn49zeGBL@2R~P@5J>(>|WJkr5AbK zP>3CS!}V>?rTUhsoK2d)U$vx7&kBL7uCmJ)%igNXNf7e@6GJJ{n$xna9+i2`7&8Tu zHp>ty9p}3ZT0@1~*vTR~?EZiQPn*P#5k!D6g+{GlBF62-!&BQ5ijOn3Py?|OEZ z)O7iaOcx7*V|%IbGrJvugff9I?gfmYpjvZb!Uc}C?1oNP;vaw7>h#(PX-|2&8OTho zK-~&qxk)oa;{3MscAHHIb_$`@%=d>nvI-UR5GXPL3Lq@(SRR(>^GS51Fg!B6mT)>k zwF3JPu68}0q*d+M5nKl10Ja)H-b=~D!zc3C_L^UR7UN@nlNa#7$ZW=CP}Y zEkqqYxk7155fgDczO3RmD|(lFxWZ|3Q_msRbpeR6pwBt2OIT2p(Ysv5*feDk^Rb7N zAC_x}m(`OP_3Wn`hD*;J&k6j+6!_F~@93-jx(xpCFj8NH!kap;iCVPqBRs~79*RsE zEeLW^38G|bu|w$7k*cHs7KNbH-+tS8CQm)+Y2T_Gku7fGjoZ9oCmG90_}AXpi?tI}VY{$|N;pxdv#$kS=H(JnJTa5S491OLu$Cl|6 zsO?c)jr60166Q@D{6l+b(6PhNKcHa;S+Gpl+F*^bj_Tu2L5)mJJ=O3>fZ0F_ zPzu5S5DHtL4C1agPut{TEeDP_pLL83@na!_sZe5(OWi(~%fWDYv%vw^9d9tQgp~JN z_0^+eg_Fn<0_(ogI^H%hDuit0ajO2XpL3M#4QA;a7_To$Q+t)fiS{5V zp2im1ynHgGerg`+^=MW{I-J*NJ17j6Zsl-yoAbS1XPExcT3vP}n(>W|u(n24Sx%#i zl8jg1T_~p5?}%vR!z05%u)(Y%E*Z7mfgruO68Xi7-)!+nG5d(5`E`gT9wCRg1Yj3I zKzmg|h1L3FPj1RkrQi?|51LJ#grEv^>Alb@<=9H(w1H^Y`!hf0`0~Yogs%x*i+SoZ^9=s!T|qO@y;~^YU^PxtpVs zNP@7VcGLZ>@aIX<1P%_vAT5V{Q%_ga0cDECE;1h%^wyI%(1BmSecWbaqKo|4$`Y z1qM(#5EPDyHxg2IQ*+$t1Gb$2a{oEUleO}4Mf{TJipumv`W|oB@ zkrh7lMD(O@HB8JB>Wmw{{O*qus{i!%fz-3(3JPnWa0oUWMJu7$1fP-z3ib)=Pc3@K~U7p z%`DNmAX+%+>>!E}DxG2q7+gs(pvim<0}3g$oX{Z(qZlrF3=E6}>Xe1EP9`E4u@{0H zwRD-!N|8gH;-I<*%Lj(GL1um@5_W`XVz56_hBp4nz($DwoWA`x$!!DZO(>ZJz5-Cb za#f@W6aorHqzN8_O{MWd_B%{Z6`#-A7k*tl3r)JT<}xWO11Tj#f6Hh#|B2&>EXwL@ z5pWvpm9p-|gDQ=Vfrf=ponH@x+on{SJMMPkc}gX9veja;uV8joB4Qdsjl#q_&nbPE z=pfb$$mpJB*Yk8w&`|Zw%n+jcM0Tc|`XPZP(cp?RYcg^qr{#5z??X_AX?~1M&y=Xy zPt)1V1_q(+C4b8NsiTgYINg-ZAyY#iX^FWx-tjk@$d)(M{6JJj7+RHlXzm4ynIbOE zf|>4oPWm-wLM(EqXvyITRR0#)FV*qNaPwx-Sb_xOKeS^q5O&IM8sKns%*JXcD*Z&r zB@e+rLgavr`6$`C-Q`6} z&MNJS^6o%t*~0lYT@``PM%C+|!5^r-ynp{)4ZdAI(@RffO8EKqch%sIjRFT{x1Ze* z&K`XjHGIsBz$fE(1pTchmz^l{DHBH!7RL%b5rBGPB=%>m3ar}wc%+sO7mNNZ*Ka29 zeq*#@7Pde2JR`k!f5p%!o$&MCQq;A7mAcdiirU+pDd~t~5wndIaH%pL*_VQyJ*Wf2 zSnT>;c50*m6<{*#03@A`CIYIu^U~N&o+i-#?zW@v#qSV}%L$*7Sf_MUljzq{pL8Ih zgEcb!mWLW8g|7t{PFb2s)q#j7cpq!8u_T-y$ya^_! zSwtJ_9rE#yMUey475XGmac=gc(c1$mVSvVnN7bv9420t4YUG@*XPMr6fF@iSN`q1`Vi#Z4tO_p zLP5_0AR~h)0E{8o!KJu%@gp9du}eYrXIL(@fp_Dm|E7x?JB8hU$3u94_2nQ?Lhtf0 z@_XLJyYqpJEKL0>M;d)gGc28Ws)-?VNw+!iZtUjd$c)m!Pi2u2Le_TD52fC|vz89KTc~qfr@V z-AGj_^^m4yl;rwWm0Hyfo#M1AMn<$QNOqRg+QMa=*}V{PYS#<+?K}E#Rj{b5Ml9W? zFfurQkR39S66;vCt_OfJ16a*NTr6TTa9AigYN{F~l!IRV_BsDDRMGmid$FQ)M`JNt z`D1!bsvVB0U1wHK5?a~|$i)n3qgZ|)iQYvOT00zxsL+E1fi$84vbXST&a6uc#WBVyV!HAvGzcGXjUg3#SMHS&1 znr*B~$wa5mDD8C0YGn_=Wc*#-?mY}tp*+?oZ*4WUV8#pj)_3*&c#+@97x(p(V^AlP z+S-PG!b_b zVM}B>L-bgB5Klh3!ix?Y`tQXOu|-S1PofPh{tF3zzvg;GozrMYHc$Bh0A>JnY=e;W z0DB4=);rscwJ^Z743T41IzFfYrr-g>ScMe9#=NFh;n)r_v`XEH1wAeo4$C zP2pGps#^%np-&a`Ee_t;?1NEC<6Vd^9q@6p{G{l#zR>J84UZadoS= z^0bce8pJ==v89g>VXD?wOn7$vlgh`^EEbfp&=9BbXaNj`S1X86(=6m=aJHu30 zz2a`?OQ+%6dOVvH&%T(_1m3;|^KnKJCaf5XWPwr3%LF+2BSNO<=enBdW$nMVAD?Hw zJ=f24C>`N5a{fA?RZb->got)|e{ovX&QZ5N1H7t=hzQb7VJfk4Q`L~IGYi!(+! zc_+2dc=2Lt*oziE+RE}enZQVL1A5d;pJI^)4iQDeo1=-_F$>M*9@Yi2X3bTKCuiPG zuFSvVnNLo$!IY^gBic18ic#N2E{4B9v_4TO*pBQ9E8r8FGmyYwOCK$$OB01@j*cnk zovATm=gBrKza-w(B7Gq0=zO#R%+OOZUQE7(K@ zpEG2PDh$%66}j-)0F61u3|xw=5fdQ2o5W$upN@`S7i7>0wiq}R4-gULgQn7?5XM+r zV^XN3SEcj77FO(^<&mbk4?qb_oOl7!^ERAu^f_C%vLe}aLXkY1jRVq1oDqcC%aEc7 zy47@W1W_#wX-3blcR?pGB%?F!*=FvKl^>%S1(r0hD93~uyLmeO$un`vo-$j2I#-T* zNSanIC?M6npgD%CD>g*$I@C!yKP?m|aa_IHN$QF1pT8`U&BgY|^A zMF|1T7fB}BvE{91li#@wakVs_)$jc6eO{bg5gu_b7^P+kXutQn0&|;}gw9pQE zm0g5vc`dLUTcC-H4^xmb<-x;7g@jSuef8ks6R06<;$wNzkCT8ouhP$+hT39fQ>KsO zgjkUDu9A=MJ^N6_j+9s1;>*g!_G7U^mn9lT6;D^wJV${8P@!a+?yr(N0D##$Tr-3U|G*8d%f*v$RYlpIB{?D_fuIU7^*Lo+M>a|1>W{qAiwBhxx|Jd$dyyLyhAX^m0R`w)aAgh)!Ek5 z@{A-{E+@FIKM5*{=cwQy_~0$+J)!cn;qx2hMN?;uy=Mex_8%b?aN4M`9BBm@D!+z) zz8tSp35-w(hOt7)TclP}lSsf?+#zsL#&>eNIBZuWezY>~!T>9IOZ13Ej0zt2M`SOf_RhgGC;v~fz&4ZDAwH=uP{3nAhC;)YoYV^wIN=&sKf6e{T2(Fg3tmp2 zsUL?Y!N*YHn#6cbko?-6-#fpG@8_T9w7E(z57aNPTa5W{mh%8C$rvgph+q%pey~p| z5yO;G_|?mJ{`MBEiYKmcdGLeRfTxpfNT+;!UqCXQUuWgsvk1;GR0jo%{4%J}UPs z06=Bpl+o*wX0j4gBqoun6u|``t(>Rhhkb#qe#NP_(*q>Qq%yrB-V%aHR3Mf_5`~ zJY;JuoQt%y(X|ZKE;?Y?fE&5;M9|piX*iGPE{RhMv|ye(Hc{LAQnBjt`u33}^e3Dg zk;(T$fXxFKbg*j#7T4~23cn>v9#m`_bUyl)c6A_SW)(#mt(sznpRf`xIz=SwPrCPF zy>K|CE|J>!-C{#`yA?vFUPzfpG)ko*jX;$@JiHa?{chO8*+cpZhDJq_ETZ6r&_wH{ znq3i6LSdw)#zeda^w*h*e?_M%qYF_=EL?KzQZkN~EuJR+H-^#yi;sp%3Bt>vJ|7rT zqRP6SQuEd%GU&y{#%=hOUjV`Izcc$^tC6QkC@{JnY!_@0QG4oq;T2@>dod9*T}v*7 z7`sGFJM09f^lHxC_E17Cz*`?a)NmgGzO;TBoOdUQ)k%*NmrxU-9r;>N(4vQePSlpa z`DtT0=b#|dP@n)zxm#((Wp}a!l(bL%9CcdNT93B zLeggx&?9Z_X?Z=XJme%8WmgFchq4`rGHS^-ovwJZ%|BR5UnnW5t!b#hUq8^yYA^UO zCLd`@exc?NGtc}oSJy>SbG+oV+_uO<&Xh5$muOcs|HN&1f9hV~>Kow_*Vsi}-4!sk z|EYPQGt7XgKrWT10r&1_{-byze>~Du;efbzh%{QU;<};7Fn_^rIUcj!meT%aYJ@iV zF#y13YyQP8*_l0!3dNQ>pT4N^GmEpj$@1dCp`3hW2S9+l;lEn02fWl|s1(n^6I)(i zsUDTs3GaXoV!i9Ah9uW}DKmDrQ+d+nJ3ZK^bn+Ou(d1_;fEMxNK)?9RGF{p`lE|8C ztbcv1{aB`dqG8d!#M(Xhq*TEeRf8T^1t0QqV7`oTZC!=BOlrDNS0WNa?u@Oms=!yg zOYD_GbDx1{ZeYY|R9cnIMzF{!W3)p}Lyh$nE(V2I(Bjhax{S$~LC=`Og34R4)}q}e zfv`!bE0yLPp1mLpa;7anuWvFlv~jE3Q2DrJy~vGsQKQxBXuGJvJiU=!Fd|bJpEnOuYSwRyqNbW>`HB%m$hlv2K=%QEA{V;kv`P@iDIVXWJQFfr ztP`-@8{IvsYJ-WK-1t}$cEsfG^nqTv`QF>Uq4}(H-6p9IJK^&R9GQh?`iN=q2I_BE zK?9_N_~zW8w?jsV0Cu*Tf^EYTn})_(O`W+0bxW(viZtM4Bvne_kT+>|O?o9Q}M>b$UI=TorDl`1c&`E5Wz)+Q!qnzN?y>A-S1wGg8bhY+5jxqc{9N`E)Q*R zP~-d}E-LpseBo_wb3AAgR5Xu1A59r3Ob^u-H3*#-<=h{FR_%^~B%$r|CK%(f6jgbyHks)x3Odo%vEJzzLjAAK`@V+-G-URZ%VJCsX&HAE}R&RgwAuv~!*^3gM zRm~d($MrzR#I}`5&a{ z1qJ)SPA^;ugbJJaM9W^7JOpqQ?uuI23vOql5k^PBdxi3*?P+Yf#YBJ&4}rcZ>&s<-6_q7+Jra(uMhZC&0th0j9L~<>VisD=E(&fW05(VZ z;Fxa!Fena<6FCE*r=z1K3<(9B4~9a&`x7R^LcnvNITAb8b+P}f4*y#f{o?x8&VKy% z|8|M5sBonBZ(^+KA9|~**h`PXsXzb(IDmTzC?I6CdB_fGikTlHeU%+7u2vH1Y%^Ex zR+JpBW~rSk565|`#GMC)3X{!UC>fSU!${C&1EIJn7c`baP@Bl%NY-x4eZMUm{@Gf z;ns&j1&sL~l%0cZo?#nk;jNoPPye+Y^2!~BMbdu5_JbU?g8(y|onrUK=SMgG7$1Ry z$MD<~ZjD2WsM9YVW^0lxgVH5`yZTA$U^G!R5?Kbe__2bq<2+TqUpzjBTmihB!=BF1 z_<6*ieDpnDJ#3q)b0g1wrNX6+p6xVTKls%OLCNfFI4rE!wpsq}J^+*d71#X!EG*Da zivp~f_yl~ZUO^>3T^kyK%o6H z!7i`utQ8h{WmUzEfSxX!NItxpNSzw(o=7pqEY=^@*9>tIU1&#m#Z{~=5?^+5hPAIu zcX$GWNe|YZi$iTR6JY9yBr6)GaBj}6xRf33w8e)Z{Ak?IDWR& zpMTu-(EnKbG}Hf>wM}gp+DIQ-a_Uw;y@jhS@A9Fo@NW&X>`gR>?dbPlgW;iZqB{#G zc^wDJ@Dq?{_99DU`3WhcFPpo&)+95vtvmVN`LTwldyy+G&2wQ@cSVg%6n~9b= zXdQ5SIQQ=2hZk;gdOiNw$h|tueS@=z!1Pk%>ebl(nT|06Kn_hRhLu*u3?t4_uk%Tb zK(eccaPdUx5tyAb*oZ8Q07j{ zIYFQjTx)a3b-biRiv>Q?s(P3Q&Z8g2jr zAQ*O$e~1%$SO5x3ngGkf&LCr87c+Tqt_f^`F^5s3VCSBF>P%aMTcbagI#O&AONm<= z-Si)3Uf()$z01`Tfdick{Qn45fU)M?HESE^r=nR;j#_gmra~gP*$H6f z@DD2J9cRumxqA4RzjwK)vjr{q3j+#uF9QbO1Jx!qbR8<3)Oe#qCOA@)opaO6$Qgo> zv%SR4^_@UEDgsU$%RIPv81@uimR_Kget8|?^yIst%4g|TWxaFtX1=ZyMD7o;>16@7 zFepDtvpm(&bZ|>^-{h!aD+>O~20N<<80h!P35c040>p99M=O%^BFxdIwQw1S3iZ(c5n+1eb<7pz7mP`qM^rfoF>I`W8QY?mg zFA;3xIfE;t?`N6GU7?}sQKnbb-50%7H_O+?@T*IEZDaemsCDZASQZ)l+*P_D^ESzA z5X7^x`;95wzvblzk*$6hzFz&7mq-ITn#NBUVVz7)EU%h&p>_Wd5b%E42 zC$}mrq9cBv#_mY-|O8?=k@suU7F90JD`#>=E}qE*IHNA^mOpg>Bjfk z4GI8GBrS+#EH*cB?vsyLq#iPh+VpBz(j+L-{ri)^(M23- z08$3cv`q#YAR1KIcF~iZH#j#+93Cni6~(^BP}IzKGp5N+q+X;(VK$XL8?Yd^Lruw`s%^tr63%i{E%Rm43 zx*^VnU;_Z`A@KC?3;-CY&pw=pfbR?kI!S1NJU|ADFapQg(zn9_gmixk>*Tf6svlf_ z1Z`sy@X#SpAn15!Q|WP!<)&j9T?e|@rtYi#1KV~o*hQ-&bJi+7e}q#LjKJBKo=knW zIhqkp;#0MpxCC)(kt%t4)ud00gJ?5%8qrI9#C3pB7=dJ}i5WqQnn)*6Q(Wi(n?38F zPB0Ty#Ts;>eiVP*!{FFLPq?O4c*L9Yti~d?Aj_# zyS`s)dB4(o{50_F>tdE8uznoxVckdLwK@j*sQi-M|K+??qbx7N{HL^?PuWsE-t9%9 zX1KY^h45#Vz^CWoCjP%tXop=}X~r3&hon~l!=mT?-~W43?{81Vb-J%<;Trp3EPxLN zDgb7$Fvtp-j|N!Ed9Bj)k5CDKj%86ye+CTY)#%{c+z2FKtDdFhx3a?{52es=niCw` zUpi?s*+(a{ZqR6g5)T2anHZ(o(%DzPj=d2d-9`nAy>WWSMHT9KBTyeaR`J8f^AU|o z50EDhBxaCrx(eTCe>m{ZmS$my z&N`r6xBjKqDhF>1Wfv;!-ms5M;{-?P#~nQfu1~b$zjQVp#=GePs(m9h}S$78H|Q+Ho|nWy73S27f_cy z76gQk9V`egZ6qM;F5e(~PTey6`Ey5RgJ4Lj>*!n?c@qUpvwL965TfbI*#@Foe zR;V;vaUo%YZY1ZflMw$A3Wd_T@mE9@!5u1QWdOc+eQ1=;Meh1U25>_BfF~Kw$S4T@ z{GP}mgE-5p7Lr0xEHMuiDP=BeXeBxESE@+syd8`s->jN;#DeZyrCXQv`vC9tQ=h@D z!FSlP$jTs9IN$<|K4KC^m|Bq!tsf|(r71nlc9%wSTj_cP}Jg3@Kq}-Y)z-#f>*+>(zF^YryX_!4zEv|3x zd)zhMr-L|-W3JKBl-98ORRsoV!l$BS!>tQ+IIi@Wv*DY-Q73^PU^WY)hSx?3&t#{H7095 zk7y&S6(A?4A&A45TIClnMM|1{oycr=8Y?lz@Z0byzAg#jB87S1h!Se>skrkO@#0Y7(^_1g4f8@Ux?Iwb@ea#W-d!>olw z-hPStD>*F}mUbmX7eSF0ACuE4sqA|Z-qMh}PvTwon;#-aA<850OBSDaWg$sF9?p{S z&!#SUQ|`hy#}Z`aMDxpHO~&q9&?dkQu9qQDNnP$b{)-7X)y2p%K;nxh!}1{13qd8? z-e)h}M8Sa7!Xx8hS*24EwwDNl5Ld@a5begD0?+VM5Bd<~7ynOA>A6?byNe z;u8#Xn94%*`wsj62n|7LWq2#1N)SRt*1ao9GP@5N-Y!;V%h>DQZhnl7FQE(%Q9vB1 zv7#FJU4T#sK|E@qq?Ff>_MUQ5ytR}rr67e4pB1+OVToRz35 z8Kp*skl$zBiz(q7N{kY>dsUA;}{urSuT>%m;qEV{#_0UsaX0$A5ea{$5^MW6h3W zJgfZl?sCX?odClqYR>o96G@?wC)P`WA2zMcJ-Po4yrtbU(W+63C%se<+#C?-LaG~; z9fTzO`u_I(GobzMpP=yw3iHxiTdB5X_+4BE?0s*1Y3V<<%d?4s)lP%%S@;bFIQv5c zG=zj^R)ul|N(17A76bj7%L)95j|Y0{GviYsyZS?|~!?5ri;^ zVGQ7i_vXod(MmX+#x@?UDW_@7X>f=HSY*gj+HVZ0vefEMdkpJ~l`HS%5w?0KvwR3I ze_BZd$XeFiM@7A6;>4+eS_ig)yf1d$K{NO*K{?x8K%OfO=4+e+juOI1*Ex}281|3i z0L(?2FR`cYvaFDdbEdG2{Q%B3AsNnYC>&*IL`>u?n5b}yYFGwK$xtfueQb{S_?55g zZ%?nb55Z%HP1flzo^7l@(%C=l~jAl5~@J|AK3*c2&>7BAL&NbhN;cHJw_44qe84&T zv{J-O7pWMrD806SL+EEH!DRx7rPq5JgX5I6qujq#uBrTC$1u_BhFQBJaa|iu>u_m| zrZ`5eQ>mb2$)i4!!37JLZ$o^sW>mT$n^NmXvz9OLA-+^gtViudPfFRA7Uk^6$ObGY zSa3gE#(iQG(&+XC@5+O}>X7P-N)$cNQ#u2as;CYzDAlmCM_~O3YXG44plGzwDQ$<} z_symT*<$b`lF;ZP0LE9u2L#CTS24Frj!@v<#!j3ExzSspGYk(3Lyyyd2yu2^4(ht) z_4X*7bVN86DaCba6R( z^O!-ulB;e=seQLshJD;5DjMq>GL>O1B-v@U0Ro61kDa69Sr38xX#xG-=9J-8xeqnIZj^~=PZ8Mp9^ zs?skz+`U|xRBr6D6@0$$jQ>ss>8N3rPfw!LVH&g$3w?Z2WTr**;L}6FiyL+grVPf+ z;6P-?fYl^?PY$+|N7o{$nQy7l`F@46cCGr5F37rLuV)lDedU+-y^Trdcki)(o!INc z7Ji~>8Kf$Mi#U-`pEl|K8~%@EN{7|eI|t%P7WUY=-51ZCk29?UT*-MufSW=K_b@;? z9aP)Ml<7?0V!)arlehgb17*+kC2{K=dz(ESF%xmDybyQ4$2Rm0jTuYvndkRp%Czj_ zX8+@^0%T!%uoC}&%b|7`AflE#bd`N41h=dAnS*d8V529-l};JNZgW z@j6^o&@+_3CP4}WlRwj=rlr4U!@@mZ-GUF>Sk3OY2|WwrRbb$PhQK=uf+l zaG#(x^X%1-)N2bXDb|6;NXO!ih#|{z%$V|Q1#C0KObI$NYC?X=l;Rad#Jz4W^3u>fLRx%d-5I zU)s!K*t#pj|nDn%&E0?Q(3Vl3RtAjSvIC6hw1c0^dC=m##Yp*hr zLU6L<{m4|zC?WvX_h<}wFftT&m`>Z!e;@!Ykeqt4QKF_iV-)tnvt~6j3U$@j6#Gb2 z>Y>q{eh&|J5Cg3-$G}TXz@S;RGrcj#9$?IQ zWYk`BWC$vII~|!ur<4RlP+>73C<;U6me&Nou~_q36`Ap9Aak!zhL&S6^_coT*-o{e zf%KQttRNls$nCL3`dDQ&jJwtjl6H&9!%Ed)*6AWzUm-IyAV+xbBMx;+`qvEvPK-x5 z6dF=?H-5-_@;nlNWwaC@mapLuW;L0vNhIZ6W6J&BaI=PzGb?O|Te%n?Wh(l&XLG$C zi_$pkH@r5bsdalSWGz4bkUU{)U71X*D;TNS$M|JD(u3wnV8+zXHBRr}kQzO(>1Knf7R{=R}kc-KyZ4_UTIkkY`g z8@QPbhM`cdjY6ZCCC{416T*B&f=z;nQPMJ+4pE}mc2LSbTQ7~z*-|shW!vgS0N9u_ zHPU0@(-7fhO%Uh)=VHAC;9Ul?CG@dOvvMA+bY4S6O-K$%+rUtmSw`aUT7d18j6Kz8 z5<58vzSE#W+(w6#xS4SPf<(3vw|FhG(?8dKBH!Cs9*ii?!61}JVz3t48*Fw?AT3#3 z3Ku<>dZQ?5L6~7Ml<>AR`p}$7zEBlL0z|Dte!#FLh*`PEEqApk7_~W&z;Q@`-;ent z8;_!M6srJ3?68*XYF6LaQ}!PePM_G`wgbJ=Lk8^CW|7-d?dM4F3K1#hmQ1TO5RlEM zQON@_PZe{Ky`FF{*S@t~z9ibZE?%GjqBAr4LYjRFA0)Mf>JK?SVm`UVN>|<%NUT;h zmLoG-sJ3+Pzw!MEey_Lx@9WiF-j<_ccKmFo>-+zF_1A&WrMc{Qx$LG8x)6XNuqA9( z28$YsL5O=WwtSm0fB_L1|702qnm)C0BbJDCX)0C$8v*^O57_<>Rc{p)R~K#T7Vhru z?(PJ4hr-?6B_tsf?gV#tcbDMq?(PsYK?Bqw|GsCpbJt7NTJ=(Gesjz89R4N5P%N6_?)Wc;04&pFLTlY0RhZQb7EL#RZl!cpem(~5a3@o-pNsL72BbY- zM~c8iKhOAHM((#fz+nA{+Q*;oK98ixAtZb%Eaea?A6q*a9DfO-)ZBuVNOUMSdFM;& z1ISXYnd_0q_pobUY1|oug~!v91gq_;u1RKo2v3wEWA3UZ;!nornXobWB%HixzP)I` z*4~1_`_F-Ia(4MQX-UI(#Ho>&$!XKtf{~$F7fIb16&f$!yS+F%oD(!|mKEntD3*=M zxN2e)X0|U6mY?RYcUs=Ca> zcP-=~0EpRXC{PA)fC2G~x-e~V*giMe94>bQdm(ws;?_hy2V!Lo5mzk&`z0-1@4yNd zm<%@q#PhW0mV3i*3;cH)a&>mCnLS|eWmav`oPBGKh|Ui$3v*w{5>Tf)<|E7zq21I) z<`S_2kE9%1hAa~%rba(<;LW?L`OSXC%t=HeEScnDngyeFX~~BvS$1i){YQZNGK=!M z40Q1wLD~QSlKEo-{|HrsaFvr)nLmtE0&DE8QVV?J*6q01i4t(0*79hFABGB-Y88!{ zkCyY=Xk$ET7OEM;xuWNvd<3{fykMF56;&WsO5^$b?`u1*O~5uwKlFSez~GasU&VE2 z?)*m{&Kf#Ov&n9TieH2ecsB6R*UTrpy~+>{$GdUG2b2fZNUhRB2m~n)EhV~e;wxh~ zEv@|Q)Ga#tfViszi#Gn-Upl~V&oGbMiaMKBJ#4Nzw#NXJYF$7t6ctf;T>}{%`)IAu zErpM9dOJ20W~92{pQcADUek0a7 zNbgJDJTa^8s<#;qoLCe0E;5EsECMhH_HUc3bW>xOWl(YuQnZG)6H&?%RWkr2zWIHYOXBY5aneQ|hagK^+Z8%r8a(yZ?4K%dK2#oy_9)iGFWiRiPv(zKB&`rY7oP^0 zK7A>!)YMpuWRTL&a72^G=bzU>7A(@J0{eHaY!CYr#dp0V9eQS{1cb9k%k1_R1b)a< zTS-8u1r2y;)K!kLet};hp{*yjQbVd__9LOEq+u&gn*6k~KPsmxG>hF#w2!KBDIX!J z=3{;Xg`z1=9WL2`Dh{z{=Cy_T?k)?go)cmFoz>2OGLYYw%62gNU7F?8G%B$U2-!;& zH`JtQQK?}cHqnv{t;97AhamxnDU3Vx$_~=T-R)JQB!kyKGWN+m+TuRQl+;#FCmk=> zbJEQ7e9O)^Z;kPJ=$i8%_J4fONxO94&xn%CSU!+SP@#oV3#4r(vj5mn1Q_)4o9O@= z_CSyXvlFn+i^>^M+XcIjp$6Odp6GJ_LKeJ!PN#7sXw(|yAeGyFs8H++K@+vNPB zYYCj&9>xc*JwCUoI^q1VBxRkKW|`Jhx0y)3N$yc8yrq-VJ|fT&7ccVQj}mNQQEti> z|C26Q+wj|=LaS$K%vMIK-!!x`o7$)^Vw}3p;teBZMtQkYo(n~BziGitd^S$^2m+?J zUZJ3Y?y(-OVwzupZ9J2Aus)22#;)+o7SYGnzgyRLACfd!Dc~e186LDD3+(%x&1>}` z<kI)5ZYwSZ!;V5|lrn=m$3b!3*3^EWlT%Kph(6}a#2Naj z-3nBC%;#eox~L4o@)}(&j+~{73#X7Fr5g8a&ljFWwyKA^N}YPATT!PQFe3vHGG_Nz zCQRiJNmTBSab!S0jqBO$>e|xaVBY6}xflRF3L*dzx!!CDMiWQj!%$p2S&FL0+=JCS za5YUIov?-k|4(~cZ$i1ssV%iqa1x;S(@o@O#1$|5C6@Ee%lFyUiQm&z6<^8T{dhfF zS%BRrpH^im4-}K54+N%{l2%)7_R^a5fED`odRq3!UNl8E9`wY($`+PE)CXv?pE$vynQ=NMzU)|_}r59yBS+;GK+VFd=f5o z>q_?5fx-55wtA_=P$hcO=o&)YqXalPXZ&F;5;K2Y}BXM7B+2f z)SO{?=D_Mt&YLCoUuQ+AY+}@mzzLi(`wR9u|Mr(R5+bm&=sZi9?IV+wO zNq77%B+Ht%f4n;G28j)PEUM8J z3B-o8EU5C#8!?g5IfP+R#d!L2@bM?hmZMi;aanZ%dFI2Tb$g&^%$Zq)AV+O)tJs_p zND18xsWyo+nin7nyUCvw&QLZ9X~gJ`^r+AId4)z(hHeBPfY>h5@mtM4Kx>c7ZdK`W zFeJ=jbxLXeAEBcUQ%4-swO=6QcX1mZzBm9&VuK>@vK5nMj&)2!O^nI`ADW8bBnJmR zt$X{|x)nMq%w^uLbw}9w;tO8VTGaUoVxq5sS=?bRI}_W3G!2gwtHI;gYwY?C(u*}2 zBvU)}(&k(`7mL-LuUsuYP-RpR*m5nj7mZ>2x`&bpE89lenD3&~%~WrK=Y3ud1E0RI zs4|!4r$uONHKyqLe7rAbdOjNRzw2p1(?`$hb%pQ$K5%G&U;zNAVf~Bnd1OBCimCPf zi2jKK@0k}0ebvskm0?ywR>mzp0Z@Xy357%Zj7b59Lg^WbpPpIF&XgLI${J7kyudx3 zp2dKpWt)LqyIhPFDc-t#N^KglI4&KtKzv6kK0;_EvFD~zDp4q7tv?y9yn5ClHCIj^ zfJQeW8c-i8wNc{e*`|UApI?xvp5ddcyf}So+~D{U+hb6+2O3Ndx+k6}f&f9N#RqTD zswYOE0TkHLr-ZDS)jd?YP!(4=A9}DC<2E8Nlz|B*^Hd3D@ZzB`Fr86O$-8|Nk1ERl z2)P0l)6HZ_$^lT`ob(hUrSyU`YP6;|>ECu9-D3ok?X3f==fO=GGhjY9$+&xi){N=j zJ-$neK$&lM=_%8}9O#D7H(%gx)BENWuSt zC-+5L6pp4unww749FNrI{wv$q_U57W=2i%(8Pt7mDO)9I9i85f;4CsqR4AO9yD z0hiEpVOuVg9)D;oMek=!5tlRSgeMI9ojpJiTMQoA@?>@W&lj_+wf=_I86w$~=26n2 z&$zGl-8g5!F(C-EvYTS%F{5$xWm{%-JPe{AdGYBgUuI&! zx4D%kYQ$@}A3;MUXKkV4}G(N`hmTIeT(`0KplUqKfz9;u^y8 z5+jVcu!<<#jkxN0ez9hvum0X8lL}D6@R=HTQb2I#bCQ_or}T`+BBFeF;I zjI%o!5_vv{LOb_l%FM>jtTw{F(@Kl0!k`W}*P9l5pZs#Hc%@yAPpX~ye!|SjE~8fF zT5rgacP0T5}yaby87yf=kMaO4$SmJivY_e*zRjqADogSweJaRaxNGi@?$M$(BD zZYFP*y=~@>MI?Pqj``+sQ!Js8J*1W}Fpom#k|W8yNVhE zein6#(Y*$qWyo6=bD{T zjPoqIt1fw15J$8i2)`0C!l?QJjs}3pYo`HrBdqMydyQ3~`n9%{Du6!880p^iekDc2 z+#Mp3gRP?kNZn?rNNP}H_K0E6~Wp!w0~_Pj2!cCiou6G9&hEFQUO9BELfo!oha= zODiX@ij+?KQz}-%tb&tfOj`Lxb5{j!DoupfLvw}*@Ao0y^zuv6P{IUrL7CA&6cgGA z8i~c62=xK0N_iGtB&1laSn7Fs3WLbOeEN;NhUzHF*Y+tqx3O3!9`F3b*i=r7O;0Zd zt)0@hg5H!g4(gW6hMGweLIfINM$90^%?xx~000!6*3aGOv=ro6t6_7lkEJ0Xcj)oo zinR%}s2n5-SG3e$*FdZGfcnIEC!R}Cz-$~>s&LrsgZs_t?o}5fpGQD|eaR?{o6Z@P z>t1K$p0na5`f&UKnfY3kOfunfX8Ogo$gx9F%$+e5(He?|HSp8HO6c3cBBGXZ<-{n&iLvpgL=L@j;d7q^t76&oF`FU~%0EHB$)CIwtw%BWGZ;J|KMy5!` zx5YP&LqsY`ARY_4I=HlFQYaH6*FsYo18OmF&?n)uaXjtdT_p^?9Nnl0@$N*E;Ov_A zLMi#I$htPaWiHmkZTWm3*l*s}B7=h%3Opu3NynB^Vk+AC+Ex9&Zt(Dq7xQ%h-2y!Y z4t5RQlwAvv69xiiHY4c5<3E9oKaX?_$8?0>wYe4^YF0AIf;PqSFu9=C%m3$P`w>G6 zAY7ghvJb_26y$-D$B(q_k6$L=bx0#W5gmT=hcgkTs?1jE@0Ga1^K7&_5P!(p=YoND z`oHh$|M|R}*F6?^MEl=|FYs9g&y4lX#ljlF(i8vy5q{I9dH^C12}dZaI1e?YVA<5fbK3~j?^iLf+ZMmm!W&0`?m+iRE3BXo(bOY^V4ENqU)}2aHzQ%cb^DiL!eY)b) zmYjk=N#SE2UpdwlEzt7aC7X)KdI2CA{OZ$zrM=$!nC9kKVqMHZ9o9uQ9p);QS?oJ5 zTB1MI^a#-8X``wOv?< zm7by5@66xYop1_OUbC#Cy7Rn>EKR*q1s1=mt!%4TS_mY*Rt%T4mA9H`*?ou$vd=Xh zm?pc;UOosHeR@fr7iIg?L%y12zh0|RDsHo1}+@pB4 z9qjAAE@EiS;$yU`tgC|}kHP>Cp#A>N4}s1l^fC9YA8u{ewQEX zqJu5TVi}$S1wS%174*RP%CCme9Cl_$ArXRb#ntoUo#XbK-@>&}c!}^7m**4~hXbp^ z5~Od|1@liJPyTDpBnm4i!Y&_!(@IK-J0;P{j#C+zok4oVF;c_sC6I{0_vrgXHcrQf z1N?z7)?XtRf!Y(XA!lYW%+L%q2rXdAp&DVV^4z~w#;LVT(YZl)Dc%B#M30q(cc zrHYmV&e88cWZ6ofwNi1H?8<|;k&9XIIjENu7F}p`dN;-Z3?7%=qTm;{;!#Ov_$r=p ze73mjfhpD5WEq|21mqkJfeF-W#c7zh!Z59qa5ViyHsHPe>|PVNdYBlKPMxR|lhzN^ zZ^{<22tx4_n`&}Y6zgvnHv&nG7l_YI&)!?4Ri!EZ+=fbWl48ikGv9=005(=7q^o5W@wT)?4Losy@@ao+exgrP~H7fkXpi*^opi3Nl?M4~HXyk%x#^XZR~v z02Pj{Dc@)Vvy=#*<|MtJXD`Uksx}{R(0>FewYsM%8vdi{`n5;9c4-EWWIFPlgqD7# zw&I$F;A=%wPTqk^noNNjA|TM(LZE~#n$9*{hBV;dLoybc9Z;$VFNg~Z(bqPiH}1t~ zYZw1>GP!1a-4ppvzEY(k8i)d-g5dt>1{DT23UUhg>qvNV0^ ztXD}-PtSd4Cp3Z@$OF0VBH~16>p9O@X*y=(g-`%Iy8%g{Tg#inY|Yf9BX75;q7&7+ zq0(tN$rHCPA4N+?wp!h!Q!iwhN;oUKXJf0^*q>}1oUEYBrHcuBX!<_vGv2JxQE}EJ zdTUv>fk**;3StBJ!Mhsdy#O5eu@LAbpGgLYXaFX58h5W|!;=24FtV~YA3c_`_4n-j z0n{ziwt2BLH$;v4Wn~|G_=r{}6iTk4w83Nw>2S|9TUgV7gnk94-Ujj3i102D*3r>? zDAqgr*3uBuiv>69E7ZZ*>HiYiN-75`k=IIK$soiJR@b_XGD7VvG) zsm#^=v5bYP>ES|6c**4rocJdjJo(t(2^Ol?IZjHcKV5gT?D1B|b*#6d0y=!-0mEHF z0BGiI3}A7uzJ!>K>_w2rgK))DqSMmVTL4)G?Pl=07?h+-sVbLh4|LhDhd&sfr+Odx zb(T0FL|NhN^Z~>c=wVfti_N=PRZV_K>|Niq|75Tg-7kC`GW;_%P7es;M%}yUzhI}c znq+@=-rgN7*sB1sVm^JUubXznVt$_Q;EH&1v4d7X*0t&{Y|fM8?TTn zk4vfwA>h_Bnx{>7O&W_PFYundqy{C};+rfsblE%miHMBSoL_htSI{If@Tjo%mC~9v zGO-be+i15pSO1I)P&`1(WyjUzg#d)&%ZUe|p`jCy>jGQ{U}DkR`ojW-Lz;C7`74*- z-=etY1;64J>(F2l*kH(u&Xt8%^^1!IU15CbqzE7~b4vc>f8XPD-kG4RFE2_R^_W|I zh4~X%OP4#Kh-sHG0>qjM3uwjav$iBZ>PGj3S6|(?Yh;BFNwW`H^vVCjiF^ZlthXt@g(D8)?yAu|P@F-%^3!Noi=7W7QFv1_iG%)T=s+}KXrv6DqJGLD1GBt1c~yf? zr+h@f*D&VHLFWK5$ZHXs}eL3UgeV6;xys91TaF7Yl5uvCo!Rd$De6yIfR@`{|g~V{B}EOls&EiS~hz1 zE>V839`JzZQf~g+;xb9Q1BsccI7vy4k1^ysm7340n6(s(`PA5soU~I&YwY-=rQ=zz z>E(L-*Vd-l1+yRq{UvUFA?X}VAWP@^xOtWJ7avXT`fZU*%Is<>o2*e(4Scjd^%l_# zo*ygw;E`%WC5dd98{O?nPF~vH7|+f zpT1?XMGJ{U!NZjciZ0rXJZlcT8f6#OPKESId2B8DZ{szqOXukqr2g-%0@f4)>usC0 zA=^Hk?KOMWB2H_lYDP{Nb|vOx+9!Ik4a=)NR!3}DQeSgOMi1_Up4F9=x#X;EV$hX07d%}(IK=hEy!9DmMrKKE%1N?KBXC@A60N(B)oLRd0i zH30$Q6k*y|midxXc}0Esz2)??>bV*4@Ny`VadExt!4Qgo9AxEd|E(u?ak5%-WtJq( zf!~;8$dLIr8${NwjIg-%+-VKIgTKQrH66U)A3v6sS1!y){|H?M;#a#!RPW)8|79(w z>7xC7H#W1>)&5elgktYB-HN>d%}r6~lt?>!L}WRsI3iUU*|DB}xdvWU>aP9Sfk~^W(6>`>s>i9E zORqGCE=2k30`w0w=X!s>T=Sq@w+-5srx{8W+$ifkw#mC_)G++dp5C$CM> zUE5aq>1?uQUH=8l&7^XI*#*)pPB3zCC4N@!P?Zh8(9Q_>_#dI$zzl*Y36{OY3D^eu zwJKWQ%QD9i!dN1b$Gda778@Uy<&?C!(g!F2KlCL0;cI&kF}X#yiVhRwc6(%p#V1Wv z)}(N%ZiN{FGu%xqR)s2crN6f8YjV44Dxy85mDq_{yGvGnYbW3<8}O3$Wp~jnk1o61 z;%gK>8Qy!!xE`WacXXT>?x`R0gjz6!xEKUFziYbI=PLH*p*Uz*3>ZRNa`>#49}b_a z5+V#9nUkhg>QUVNCNM9?#+$3KIFEsdZj&mrvXrrZHU$oSu}r|f^-wj>Cg(qNQI0#?{RMN=Gdzh&v?z@UuvM8yr zDxxw~m7MOoK07{cFA%g4i&XlqPaU`0!Vj=#50?^(} zm4B*Uc>)mt6q2T}5cc%ek-;jx{eYs~z#}1B$YeGyawXj}F}`|31=BKCfls*Upknn$ zyS*TzPzMxQX8B#|h5;HiuJiYaJq-LI1I-q&hzl5Ow1|b7^^ee1Ain&MkL(4m38H%2 zrPkQ?>J)FjTqfgj9a(wpR|(_y#$tt~HT!ch$n22Wh>MV^T9i)r6s=yTAv2YU2e7yn z+C?|L?`d%Zt8Vm9Nce*$g({w8qA$4Vg1##+7i3;lRb*ra9&B>QUw`+MIDIklsLVIA zIYKd1m=FNkYZgYlXMEEQ*JoP=VM7X#C!!l-1eUh(uXYp;LV&EsrQLQlz~&*exX4Mr zE>>Pc1t{Z$8z1(y>XI4JJ!an&aF4BN5ML%7iW-_XwHaJrepM>ZZnF3}2>5xooZO0v zaHP{}mFx`Jn{sG{-=YAXveN=c=wKA^MP%&B6Is`g>bnwbgcUE+-dQ6l2xx&;riN5V z&qU-yUnLJ$Oj#;Ch}G1Tp$nlnLJ;yv0$~Zl)JA6+ z)&AI@ao`l@I&r6R@hU-MLxG}c*+JOs%z$y$MXwJj1PVoV=6o?{oKp=w%qZ<2^3jSz znR*0y_H@DQm`KRKs|&t|Hsh&n`kQKi+u=xo!V$Q2irx14Y0SBd2VZH~~{r zHq;v1vC1_hDT{n-A{NeV`jBuqh0V#FlMbGL%&W$rWXLAL71!zt^i zbS4w?RFK&$6zItamS38W`_>OV3YA{>CKHP|d0&R_0v&n+1#k=!{mm-tpBS)^uI zgj{-sZf|VZk6O0XL-M$UFcSrfQm3`n+Tc6*EV9??P4i zUaC;N_27&yC4L$CMdPaW2D0@dply#+g zBPC}BIu{2Q@)sjc+H@oAoMNJQL4QchPsiKf_k6es!9VxyWwE{x&?Dd7s3*|?tg;7^ z$RJhdvC43lVSS=+$cI(ipt$B3sHV_ZQpxTiQZ1?`PgK``jc5(KsL-+He3f-p${X1Z z9T~sJc?R|AnD^&{IbC1R({hME11sjBkU}H_QGX-hTTlmc;SBEWW?_@|4~m(A#0u)k zVRI-q1`=5qOf!nKGejuk+u5O;Oc_RDIYd=iDHD3?rM183d_Q{hdT*xB_oZ-Ux(0}; zX4bq7{AsPV3iJ0naUCiTmv@CeQu#85klC3~B4?HnT2*nMGU)*Suw<}8o)Gyf(7Tr+|vZHnCK)L;rI`d+q)U1KK=0#N#vx8@622CSj}g!U_tz*2iw<9 z$^t}Rh4aT^c2x=)>c=U!;tgq36#K-HAu}|`?57U6UO?z7^1H{Z_0^=Q1>!a=isWxJ z6b!@?cjn4(DQH?(18A4+(q#78|S#@$KwC=Sf+C(62wcya3` zVbx-nyap`O8Aodoh7G{Y=gktE6Jt8xm&$Br80GExwF&)!TkusU>ToMVqC)Puf-US4 zt9NtiyM9TnVvPK0;@e%NTk0{FJ4@0R!XAQ9JJ%Q=lFcm^U3=10=VB+|+g&j;S8zvj zrQWOjdO+3u0Y;crk9Y0z1k2WVS{b7r(i}i$5%3||2l%*9cK|%(%)gKlbz!5eHMrw! z5uu==x@5M?7ZXhaH0%9I(oB`Fd^=P&K{1 zWwe|i8co+@pH`m~mxZ^V=jtKcoRjfl%;ebW$%U^qut|@sy2Xt?o&t;Q<-HrJT7T}*_SjN;wN4EMm&Zl98b^&!P1V3}J08uErK~zxM3Q|nHoXr$^bSGWRK7e&Wk$n`Hua-?Xvugx$xg!W zMoU_1{1L7bAP_aP5`VA@z4IXApXSHMB;K2^7C(d&BC^|1DvgZYN|c6-j$ycDSrVj+ zQq1d?AHXL4Cb0VLJkm8fc2}C=2VIj(J}vl%@av6jP;?S+G&Oo3eXg^GMN`>KmPay- z?I2q*eob4zA#O{4ldCd0>g(&L|F0=8FqJ&~$3dG%Qt+V}Ng9@rT%AS-KmwAsURX`~ zf~nCV&h@x48Yqgy;8<%f&MbU*i{ASFIA|&^te{{hE>wg8?BkF0rSyJ$jp(t(e$(MC z(>eBL@;$gpqyniq3a2kb(X_gC^Vs{&d4(=(8UaU*Vu+}P3msc2Uq6o@VV=`khN4%< z_CSw1ToM?R+69G&Wj4wO5^}oiv|X}>kyI7a8{)h%`Q)D7LCQ5kLfWQkQ?dF71M(r< z)1dR)8vZc4#Peg*41SB7IYJf}M=PomOH7*mb69Q=ELIw9)v^PH-n*}RF-R*QYJpeQ+7su*Da}j0)ji<0CbvkS7CjOAIX=Jn3 z+0iw{uxVxCy#%JNlJVCx36_v{7V&!xqoECel%N-eU?{={dZCkID`cOzFV$`~b7_nm zI3J84M)j)0p1)3Ow&r^P6q2BG(WwQngSq$#K9M|2@j(plMOX^(;CE`ovm5BxTWtkp zuX=mHUkXEbtp>QLsXwS=f5^QzhkQF<*l4$aA3SUoTsd=8wh?#OTQ^d;)~Q`jeb4#+ zPBu1uBL8;%i1NP*Rbb}ERIik)Xz*qeBOWy1EQm2$79wPob4GTQm-H)vgPdc+f29w| z5V}SIYTa1?#O+3grP7SHpC*Zo3@&yA@z#Mye!31J$c@3G?mH<0EbVPg9Y&0*$;?36 z`Nf|{N>$z#qSyW(-Tc`>zp}>v_f1dMYmM5kVV8-c#m~bVfxm5>7YrSzFD^>YHF2V~ z`g6K7m~={wb@rRIp}*%Yd!TLlk&G`{&i}=nHmFn-l2>)kIaw;*#`{~*e14ZuQlZ-z zS~rr)s`d8nHzEA^HS5}A+xI93tl#P5uOG%Rx9;%=p2XFcykYn_L+WCoS}Ly;qtzu3 zA)T~<GSd>LAKSdnE6qOhmoX$x3-m{BGbzY z7(85fyfMU3`+f_v-`p*juf~&n6Hd>Ls@D32F0nZDsgZToZXe%`nC(Z=m%0PBx*`-$ zv*jNW2rrnn1ivPu&37xA3(`kn46WY;s5kMDnkihgB@Bv(5DIV-RUR2qh(xv38Qhfu zFy)gS989Ii9dv*gjj&R96ow6{KlDt4ng5^_Iw=E20dk|5o>RP5LlT9*lI;ggRfMY1 zIxtt4a%A%&zP@btD9{?j@5+_qxEVkRk|J&h-mtvxQO+cDsE|W5GHF%=Lv1%|{9B(N z-JX7OfLER_FDqb=opQ^}!zcNnD{z(Oi}LB+QRRZ`HidOjPEQpIRVg?sVjsLc2}zyd zB$4D-lf-DKIDV=Vu{e%uu$V>4wCH7~s&Fujx@nI#c$PO^5=}W$Mq+J-v>ous6MAau z>dBJ_aWo@N<1m(091<_IL#hDS0dUgzZq_V9J}D@nYQe3}U0-vKQ_d9W*c6<;9cuq0 zgaObk4p3z=UFv^qI6EpWX=4)HkB94oW(aOhr!7>^D~ew#dzU5r{VnwuSRJ4D=ZRap z%YPN_&)wIAJ8$4O`^}%<)r~hL#9?Y5n&>Gfx%uX71g;?6#cDgH!}5H8nT$>nC69H) zxV=U<@NXRN*Bob^@yQ?%wQ47~rjFA9*%U8UQ+KL0T3s%vcjpx)QzEs9E=DneG2XWR z>Ni~Y8?K&TN*uzbGE1RH!{gEN+g{#7Sb@;F>e+i?`ott?qzp0=y#tRp1;Wgs#x|4N zwYWSJ(nOIXW6q!xieHCKt+JDLB_aR{5vQKsz-bzJdi^{gkTJ$T|yj>$zijWGql$x<2y= z#qc{Wu=>%ss8uHR?VKGSPt+b(@2NCw17C-m(soGg_7SZtBV{0 z_13HK7XYOzawrhw_tZMp`%UaQtdb1HyqEqBnem&R8>ShA{!8e$NHX^gg=<5-l7ED7 z0a`=;I?*CHfw|S^^(lny_-C{aqgt2c#}6F)Bay0|E}m831O92zJbT`ML)z^=N9#;2 zqn}tyl@wV8~YkO3S2Xe zO$9|Go`#}z;LHmR7=;h(YTSuOKUnEYfeSJt$_mw_l5kGS>;B3HZPDJC3$G90` zLE`sFSt{>^=jRLTAG|8}n%TFt*VeW1+kR6zzcZOrFuCyD0*&Szrnt7iZvYt#00ij6 zZbN;aXSfKum&HRPz}Uxzf3bWwr;;i3o+P5r`J6TZfQ~ur3^Q-djLW1$I;B0HLdD%( zJWr5dtE@$f%tzR5@K*7=Z0n;*wWZ~Y+i60>e-z&7TZM?5&Jj{e%|HFr0s^)E5#j*> zLsC_vi`FEH8*Ok}(CFDCi^%KJf|CKTIEWE(UK!E`?nQxveMLs)^4Q9tqS*5E$mI&3 z+}DX_j-_L{VxR}9a*05tzg;?IWDd4L_`*W~03Hf$aP{|8?+Pq3ywzSZu}w=L4{dUH zRe9jvcQ1s^b;j zM(au;{mFx|AE`$6rj9tGoo*Mm^fECQlFOW;e{GiOd2}w;2wv z##WJ|)+W#9~w28SvI zfA83wFZ3u{H8MFE(C#d)y|!5OaNf7BcXvir_kf}Cn}?k7W_846J#JwDa)LPj2&n_G z`W$F*mH{Z}QZ%gsbr6wK5&<8F93M*qiuLFwXaS0@nf|#uH8JSVoWvyXP}L{BmcphO zYW%PhIDMdi5^?P2)N!~Op{hNa8Ol0h@$tleIQVk}ou!B0o=rI67qyo%j zobN?~45@BmDr=m5@W=>?Sse1L-rd3pZIbgZV>VU%FcuB^`23VG3_65J2zj4rjg>7s zi(5&eesy)RUdoS(6lj11x-h`tV?ARy@%6{><%}W&Lw!wT2`w+Nu^_%xNUtRn4iX$x z&DC$wgSVZFou{ofvdg82iE|A`3s`72h^WB8pjm(=`2hf67GUa0v)D1Ur_~`5Z&y~9~@c-PMty{EPr|_v_ zFXP@o0U*tkAR!r7XUWmw;R6E{pxKv@v&=Q@KtuHURcs23^TaSg%~j2~jf*&a(wG#| zhIVF_5Yb9UH<7gi=q4`)FzMP$EA+)mUc8}q?EeVSf=HWVl-siakm1bs9(6YmlvxrD zOJ)2*%3JwWi<05p48hT3-3~F6$t<+Iw`^gdJhHjQLYUPan)QD*YoJBXTk1*OfALXY z)%R^9&xzyG25O?KeP;|NiZIP+xb zHzI5Er-t~eGvAt9m0`ophEQ6=ySvz3?=PYu;2Q z&B_Tq=M?YzP;VcL??SI0k71;NsP1ppMpIE|&f!@4g@y*Cop}h5Dm#C%RU+oOx=lahRG|7<1le<=Icygt?|ugF{0+Ah?Ks^+00TFPGH; z%fugy3ODr${9?{M$?)*>jj3YhX=k_eS)T8Vt+nK zAppL$H96qJzF`pyUIS=g;F8l6{uOIWgDL42AqG}Z(|ydgp^YUmv0N%R^N=SyUuvpx z6{AiWWn$5wk+m81((aR9?e`+<--%qGcsq(AzsasI8C3K8(WcCNxcPa3eZEvyq;fQ1 zkPD$jL~ql@=fsZ;-m*MgwuI*TZCZW(p}n+a{a<3e1VD30RcBHpN+E)+LOqxZEa;YNe{*b8KR{l9x34UmDHxwdW^5SSn)Z=csl`p#Umsx0MVK_58Xnq4~9)9kYJ) zV~gw0-CEu~C^7EYOPiKNtJiV}?Yf-2 zp524DoGWy_k+(Kgn3}OuGA;1NcUCIJ>aW05DwGT`uQi27{__?67AYsqt<;PsSFXqs z6FaOYvXGxdy79cn)meZfA|g&Xic%qsT@8NFGZg+8nN)L$?V_+fqO|XT$lDBB!p9`f z+%hg39T6psbss0L!0{2so=1-Qs;ZKQ=@uZ~B54uUC-(Up=-W7vt4v_m<_3HDK;dtj%%_AcVu{8(+Bx4Q$kbwB1hNGZYvX-gW z@*b-#gnvvrFA`8CEjL=QWyHu7-tmmj<1y*Vd2#<6?~}{ly}!G&u5FL2BWf|Ri%MAItjcJQ;PP$tKz4jgDat9y;ZO{jUDkl4YWI9oQCc*g~iuVKW~gXgVzusHXFsID&h z+6y%(hM@Q2ZRU#saV(~oX}hrSG&~#du4E72DovieUgJh7JrfYgiYpcAr<33HKN6Kt zkLL^KK6c=l_vA7nZ8*L#7H^poWNk1wGiNIOxoCvB;Hq0Zzlfxihu?dm^6(qZkhL9d z7`Ga6>BtmmwKEttvNK;Q6%%l{z^(JV7@bUy-dhMfXExA0y2fpKK7=BIHj_ZiC3tYr zQk_Jv&wIVPkH%U3nnV`@76ZUY0hl1r1xES15u{bndF0oMRyc*!wp#ULmFuhn8vmGO zLj;odKT&Xi=54scl57^Bh=1|#k-^?`bi$}{qou6!D=fTKHm^?m3eMz4;wTAt%%-Li zFH<>;V}(s5N;DrF(6j~=EsID)QkI`1-eGdh!E#RgBNPo-WU-KoX7^QytENYPK%=)C zt0^|0s>jlE-+N>4=d*~(1cJo(!YXl4f)Mk%1h{t_-22F2ISR#rqw)JRQHmE&W*U<_Df|4}s22^*|SrOK3P;aYdJ840un3K^td zTz&urBik9eLnD->Q@%&$6xFOZ(aFLWEplsg5z=}nH}a+{4D<=;Hd!YnjqfQ*r-)!- z5Pco4000iip~=~#n9LPTo*5vm0x-WmyorF|QZt)$5h{*h*E zw}7hXBA36GZlryj|9{#!udpVtZViV}6MBFULYX842q7SZh=7We5Nc?liHHzG?EQ5G*63Kb~{{U!JS8FW27tX1~u`dtH3rdaloc z+m}`&gQ3$aN~_<_q^7v(%gZ&EX~V|FvJ-~QUAwhSc)Gdpw`QvBZ(Tde>>xe$kUC%c z=$w38g9WnJS%2T!f4h5`LprIo z?}GWfFS_zpB_ax1e&JdKPIYUbE_vXxMc%qE&}lai!gKWH63&Tz;Be>3KE){0fvtVo zzR-EOCAh*vY@XY!X*2mZp;>^a;Z-@mCv(7m2voZY1(1W=4YZ}7W6tCI^G>axSdAs` zCOqCoBteL13Wi^XKpam&!mLX}s5S7|#HyDn&I3;fm{HMWw;|GlH=p>9oCUu`Kal zjiVfo5KIfo@n>sNeF#-L!yqde-!Oz>4lU{gSndpZ9t^+Su+`S?YYeI6XeqlG>5KJB zg>{&0jJltE+?q!MpAM3Eji_1QzpYok(Ee=V@Bl-XmXGhCS(Wv%l9YgI*kw3s#E@oy zaj>fsQS^F4oN7Eyc>up*2qT@%4P`s)xh8K#d8y87(6>7XP$~=lH?z-A&!!+7JCvJ4 zRm+F6Sa5Yo32Vu~wGrgq0-u%bW~kh@CS|j5{oGIo_~WHN0)2A;fapnf2__lezOo&< zdB4NldM}rxu-h*O%DqQah!k(kmg2l>&h7v4wtF)9y53FFjXY-!4lvL)LLU4;jw{Z) zMB+v)+C2>^!TUefhaI3}WIveiywY<4&1JDf`I1s^U!t9~2CBDunEm-ssiBt_`3ws& z%4HKod(t5$>yax6dzi%#@+~%&4RSN$ZH$!-WkZ=XzMl4F$Luv-1@m`a=S-4=2MGh# zYGSsU#5(WwrujpIP+V`J-kQW+OUp~AOc1qS?u>|j^4C)`s73sA7cMvnafaQ?DCBX> z+twL!k_6goq}3|jUvd0+4!IbhY>w+4uWLe=7IFL-fMGw}bWSmT)6ks3hrRXh?P3fv zynlUoYfnoUtF`S@DHKP$AH+iR2yI1%&G|omT?}!tTJ`Pj9VxbBn?54cE^Ai`i@U4G z6L*=t%_c((Tg}mXAeE!B84*Mbj9}Xd$RN`a{!A;3rR#4tA8lt)s z(q}HzpRpftF8$tig@0+xJ7`$oem*v4%ha*%#SYTFl<|WbV&7vRE_g)q~ z&U!fHdVj~~Yi;s*U9)a{buY37tf;M=(gNCg+*C(VykxyYq+l8D7z)zMGG~Z~H-542 zXz6aFp++m`Z$aaSSeRV{hz~tyAfthpiZ678v)Gx}>2I9#8{NRdY=qZ-x#K8WGw|X) zyfqlwHzT4aKsCAM-8go|%jLm=tkD@~wY=J7inrnHGB4RCEMP`Cb&Yf(r|-0bT_CD` zR74?Nf80QHZvm~CrgAeD%8J$aW#PZyf7(7`=qFGres-rT==QY>J>kb6-esRbK{N_f zL_^uS-QI9=r8R#EYlEJ(@=k|u2_>UcFADJ)?~v6wL);&V{!nw=ls0M8ciA;wUJqN- zt`Ytz#Z~gCxqI7qdfKb@!aJKNdMQyhIBws@cg#*F*i(6Ecri{%1*M=XIvs&!21LJr ziwL7$%H-Q0jT>0~d#=ahV4l(u`#QYoO+A&rKwWqd*H~@8N@$s`M-0?By0eT{SGt6H zj^8WRLTaa^R-SZG6Be_0t;a0ZDs004coc?PKy+D*g9GLl*GYC}gbu%CgxTz#u&|_= z9>DM~_oF$;dip$)|A~qfFMALBC^RK+wDqIU*ots`MM12@T4~6bTkbjX`A9<3gksaI zkC0S-*(m0Bk=q4Ih*k2`KE;os&LDbn6#yfDUOunXk@Mpw!=jrB4PZ{h>kessPNABJ7pyv8L<&>=IY5!#=@6_`O*rw z?roxZh+oF-B^(0uRz5kU;yDsQtf7d6Et_iT0oedT{8dm2xnah7yYLkN58x|7*F3um z9?D>6a>+1)v=OcI4mufT6*YPW9A7w6WUIP09is=CU%~#Hq0FJyo0H*JneCk1JnT}e6C9Lh zy{XHNA&2IF4SlN`!5ofE=e@`qkmFzjUNO&u!GiUwkc>SFW0@N!#mVci_d}~8=tff7 zo#eX;nD!-Z{$EB;w)B7(i>JD@!0kU@mHA9@FsK6Rdgi?wU7vo0(no35-@i)}OSU;I zvnNw@@3=!r(^ZydjL5=Qz4H|5WQhYq9tE6R`Sg?%NTIyA6qw}c$jp1&Qx3~ogU_R` zl)9Y=i=dyKXTX@J6jk>Gc#BDgqos;{?k8zDC$a7CAOGx*-oEMA|0a4(iyf*Mg^+I)lE2 zc66wbIGq%H#Jh}>zEzAdlZT3gNVKXb%=NTMmN@IKgj%J6WcRFDapV_kZB}h*GP|f? zeYnhGRtPs)A8|8Yak78rfrK4xbBA;{KPyh3-(qL%PQ6d&Kjcv-EjiTO-=L>m!_?gT zm-IUT5E{+&9gLwBhUD7pa0R>qz<{n!-iPbDeMT8!>aJXDfrjk;LVYrR8=2t$?4zb)Mi86w(*-fdV2!H%*o9PbbxZt|+1CU% z#oXwnDwozaIkKETJle+4DtZZ=(G!PlSV@t&&)?a2(YF#pkEzq1Zl|6VjGn!#(4B@! z4*u}xgUyhs_JJl)ZPJN+o(^`8O`o=<4NfVer$lzvl0@Q6F^QfTl8jHQRG+wOc4`+1 z%M$9q%G3`n{i1atwekf*vpi?y@AXz$RaCd&GXD}<;G(S~y&J&P)Lv8hja1&PVqlUM5yH~{$B)v zhqcC1*7h;Az@-Jl4%nw1EC zr^)@gh`wahj8~JKoj3c({%TD>#G!hLYalchDypO(4!CfxM7Njzit*fz59GJyW)Z4) zi`~QmqluCBBK7k3JX4}xd280(^>_Elq&&VmS5)TS^2=(G=K_t0+VVlI@rg1<`DY}e zT~OzMDl&0eY&$o23m#%!9tc;3g{i6T>+$0!PRpjASLzYqW#8yLdsE?3lF|mV0$4M#fWE#{M=sU#Qpp;{ zT|Qh*AvE=F<@zl|7U4HbZ4aCn={@i3pV(pDbDX!7)t={uxQ6gy9wEO`IUr~b&CI?* z-XIkCUFniFeRt)&q~4^IA?%#`uh1WF-xJzDKHCBCaByZw+{{yu!gAL`Ro2veN=w3` zodimgD|^Gm4c!4U?#8NU7iX0gWO!Q>3`jx7RIgD!{tOl;)5mlgZxpD>$#*ZkYDNFA zzY73W%Mrw(Z;W0!U9u2g=&dN#Bb-#PqNLif!@VH*QK>=fo|2+uu;ip-6Z`8EjaT|( zT}O;-+5>U}vnsO?Go#&es8OJaqty9>i38@PWArB4Y)@5gooQ}NTxfoRY_45L|2r9Z?;vhrE47-Z9pjnY7WQGxB3p=$vv zO^-9b-zNJG=VT&8P{=%-)@4eW7;Bd2OK)EXRUOhVPsx-3tL z-itX_*#u!V%FImOG9%%I2AmVrj6GeGrx2QWSnar1eedg?Hz!kZzh{ewRk zF&UK)AR^8U+EIWoo+et{tc3BjGta|AekzXaN;FlZmeZddHW|GzZtrwD;>#HuE&KipKpJF|z7ewR z8s%2Lbr+n8QQk>PJDt1QCp#$k&RwY%%B%eh?)#0Iz@55})$qF>vdVbYFuz?R;gYrw z>1%UvI)dN1nywL0wZcB|E^V2b<1@t+6jty*U;Jm$_VXHt2AAi2Z6Aw?5u@1UR(>T3 z2WWT6ep}egmzQlFKMK5c{Om#%H+p~S&X08~0ogAKnl^Amu)7MR_`pzb6@}EP0~KuS z8&i9bkU)2_W~S^kx67rm;yP_e8XW=VAYVd9W29|DmK`Bn{WYZ4AjW4Ui?p0D2m5Bx zv7&p^HF?fo#_9~iZ=G)NEy^;fXp70w&sJw=JKwq_VqjH*Kt-u~q8ce-k_Kr&AlleY zfm8uxiQY4P7#*E94zJ3wVmk6f)ruv0OhlWDT5=r?ELt%y|Dx&qeDTzFgn0NO_i z4^5!X}?!$M?oF6GWoh&_;uMdMHgT&yBtzI+rx(NZSCyGGdtxiM^Y8ch35Xe(M; z@h?uC`(4u)c*LtO0>QQ<3Dl{0;tGq_R5ufpELJ8ubLs6xWeC0MbA$9SY|xY8wPogP zEOdZNxNfnqaexMy+ncqTiQ}@Z=^Tsy*KUQT%9AsB|n%|(vzU-I+{MZb2bBA{?#qr(B8hp zJH2oH<4Rn0mqMzZ0Q;lJU$!Y;JxC#6CC4zCU)@C-3Yh=|2Z$dE6B3d#p{3?s)@%4l{W@bO09zG|;gGk8c<*}v>*RHaGI!-i8Y)~kG z$M)Hy=;+OF2tA&k&YwRzfwG?faPiPb#?KEciQ1h2GMC8qlTQ>)GreK`Rle7<78w%- zs@?NDZHUiY0p+T;iT63{rzIDL9ZX=COGJ4g%^*wz4sa=izOXWcCkj++~xhErYPo^?6zqZh5UyA7H znRm3l{#_eb5QHD%Ss6lQ@5C}}nNzux%&&HvjM_z%D|0*3$q literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fe16a91 --- /dev/null +++ b/pom.xml @@ -0,0 +1,346 @@ + + + 4.0.0 + + com.vetti + vetti-service + 3.9.0 + + haotaike + Vetti Dashboard + + + 3.9.0 + UTF-8 + UTF-8 + 21 + 3.1.1 + 2.5.15 + 1.2.23 + 1.21 + 3.0.0 + 1.6.2 + 2.3.3 + 1.4.7 + 2.0.57 + 6.8.2 + 2.19.0 + 1.6.0 + 4.1.2 + 2.3 + 0.9.1 + + 9.0.108 + 1.2.13 + 5.7.12 + 5.3.39 + 2021.0.9 + 2021.0.6.1 + + 1.18.30 + 5.8.38 + 4.12.0 + 8.5.17 + 4.10.3 + 2.12.1 + + + + + + + + + + + org.springframework + spring-framework-bom + ${spring-framework.version} + pom + import + + + + + org.springframework.security + spring-security-bom + ${spring-security.version} + pom + import + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + + + + org.apache.tomcat.embed + tomcat-embed-el + ${tomcat.version} + + + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcat.version} + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + io.swagger + swagger-models + + + + + + io.swagger + swagger-models + ${swagger-models.version} + + + + + commons-io + commons-io + ${commons.io.version} + + + commons-fileupload + commons-fileupload + ${commons.fileupload.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + org.projectlombok + lombok + ${lombok.version} + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + pro.fessional + kaptcha + ${kaptcha.version} + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + com.vetti + vetti-quartz + ${vetti.version} + + + + + com.vetti + vetti-generator + ${vetti.version} + + + + + com.vetti + vetti-framework + ${vetti.version} + + + + + com.vetti + vetti-system + ${vetti.version} + + + + + com.vetti + vetti-common + ${vetti.version} + + + + + com.squareup.okhttp3 + okhttp + ${okhttp3.version} + + + io.minio + minio + ${minio.version} + + + com.squareup.okhttp3 + okhttp + + + + + + com.sendgrid + sendgrid-java + ${sendgrid.version} + + + + com.google.code.gson + gson + ${gson.version} + + + + org.apache.httpcomponents + httpmime + 4.5.14 + + + + + + + vetti-admin + vetti-framework + vetti-system + vetti-quartz + vetti-generator + vetti-common + + pom + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/sql/table_20250828.sql b/sql/table_20250828.sql new file mode 100644 index 0000000..bfec78d --- /dev/null +++ b/sql/table_20250828.sql @@ -0,0 +1,71 @@ +CREATE TABLE `command_fleet_info` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '司机ID', + `codes` varchar(255) NOT NULL COMMENT '编号', + `name` varchar(255) NOT NULL COMMENT '车队名', + `status` varchar(32) COMMENT '状态', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='车队信息表'; + +CREATE TABLE `command_driver_info` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '司机ID', + `apple_id` varchar(255) NOT NULL COMMENT 'appleId', + `google_id` varchar(255) NOT NULL COMMENT '谷歌ID', + `emails` varchar(255) DEFAULT NULL COMMENT '邮箱', + `password` varchar(64) DEFAULT NULL COMMENT '密码', + `name` varchar(128) DEFAULT NULL COMMENT '姓名', + `avatar_url` varchar(255) DEFAULT NULL COMMENT '头像', + `work_status` varchar(32) DEFAULT NULL COMMENT '工作状态', + `active_time` decimal(10,2) DEFAULT NULL COMMENT '活跃时间(小时)', + `address` varchar(500) DEFAULT NULL COMMENT '地址', + `license_url` varchar(255) DEFAULT NULL COMMENT '执照图片', + `license_type` varchar(32) DEFAULT NULL COMMENT '执照类型', + `compliance_status` varchar(32) DEFAULT NULL COMMENT '合规状态', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='司机信息表'; + + +CREATE TABLE `command_fleet_driver_info` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `driver_id` int NOT NULL COMMENT '司机ID', + `fleet_id` int NOT NULL COMMENT '车队ID', + `type` varchar(32) COMMENT '类型', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='车队与司机关联信息表'; + + +CREATE TABLE `command_car_info` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `driver_id` int NOT NULL COMMENT '司机ID', + `car_no` varchar(64) NOT NULL COMMENT '车牌号', + `car_type` varchar(64) NOT NULL COMMENT '车辆类型', + `car_status` varchar(32) NOT NULL COMMENT '车辆状态', + `fuel` varchar(64) NOT NULL COMMENT '燃料百分比', + `kilometers` varchar(64) NOT NULL COMMENT '总公里数', + `current_location` varchar(255) NOT NULL COMMENT '当前地点', + `current_location_lng` varchar(64) NOT NULL COMMENT '当前地点经度', + `current_location_lat` varchar(64) NOT NULL COMMENT '当前地点纬度', + `destination_location` varchar(255) NOT NULL COMMENT '目的地点', + `destination_location_lng` varchar(64) NOT NULL COMMENT '目的地点经度', + `destination_location_lat` varchar(64) NOT NULL COMMENT '目的地点维度', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='车辆信息表'; \ No newline at end of file diff --git a/vetti-admin/pom.xml b/vetti-admin/pom.xml new file mode 100644 index 0000000..532d24f --- /dev/null +++ b/vetti-admin/pom.xml @@ -0,0 +1,106 @@ + + + + vetti-service + com.vetti + 3.9.0 + + 4.0.0 + jar + vetti-admin + + + web服务入口 + + + + + + + org.springframework.boot + spring-boot-devtools + + true + + + + + io.springfox + springfox-boot-starter + + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + + + io.swagger + swagger-models + 1.6.2 + + + + + mysql + mysql-connector-java + + + + + com.vetti + vetti-framework + + + + + com.vetti + vetti-quartz + + + + + com.vetti + vetti-generator + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.5.15 + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/vetti-admin/src/main/java/com/vetti/RuoYiApplication.java b/vetti-admin/src/main/java/com/vetti/RuoYiApplication.java new file mode 100644 index 0000000..5c31403 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/RuoYiApplication.java @@ -0,0 +1,23 @@ +package com.vetti; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * 启动程序 + * + * @author ruoyi + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +@ServletComponentScan +public class RuoYiApplication +{ + public static void main(String[] args) + { + SpringApplication.run(RuoYiApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ Vetti Dashboard Startup Successful ლ(´ڡ`ლ)゙ \n"); + + } +} diff --git a/vetti-admin/src/main/java/com/vetti/RuoYiServletInitializer.java b/vetti-admin/src/main/java/com/vetti/RuoYiServletInitializer.java new file mode 100644 index 0000000..be73aeb --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/RuoYiServletInitializer.java @@ -0,0 +1,18 @@ +package com.vetti; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author ruoyi + */ +public class RuoYiServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(RuoYiApplication.class); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/config/IgnoreWhiteProperties.java b/vetti-admin/src/main/java/com/vetti/config/IgnoreWhiteProperties.java new file mode 100644 index 0000000..396aef0 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/config/IgnoreWhiteProperties.java @@ -0,0 +1,24 @@ +package com.vetti.config; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 放行白名单配置 + * + * @author wangxiangshun + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "security.ignore") +public class IgnoreWhiteProperties +{ + /** + * 放行白名单配置,网关不校验此处的白名单 + */ + private List whites = new ArrayList<>(); +} diff --git a/vetti-admin/src/main/java/com/vetti/filter/AuthFilter.java b/vetti-admin/src/main/java/com/vetti/filter/AuthFilter.java new file mode 100644 index 0000000..f9b48db --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/filter/AuthFilter.java @@ -0,0 +1,139 @@ +package com.vetti.filter; + +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.SecurityConstants; +import com.vetti.common.constant.TokenConstants; +import com.vetti.common.core.redis.RedisService; +import com.vetti.common.utils.JwtUtils; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.config.IgnoreWhiteProperties; +import io.jsonwebtoken.Claims; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * APP登录鉴权 + * + * @author wangxiangshun + */ +@Component +@WebFilter +public class AuthFilter implements Filter +{ + private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); + + // 排除过滤的 uri 地址 + @Autowired + private IgnoreWhiteProperties ignoreWhite; + + @Autowired + private RedisService redisService; + + /** + * 获取缓存key + */ + private String getTokenKey(String token) + { + return CacheConstants.LOGIN_TOKEN_KEY + token; + } + + /** + * 获取请求token + */ + private String getToken(HttpServletRequest request) + { + String token = request.getHeader(SecurityConstants.AUTHORIZATION_HEADER); + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) + { + token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); + } + return token; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + // 执行拦截逻辑 + String url = httpRequest.getRequestURI(); + // 跳过不需要验证的路径 + if ((url != null && url.contains("/v1/app")) + && !StringUtils.matches(url, ignoreWhite.getWhites())) + { + String token = getToken(httpRequest); + if (StringUtils.isEmpty(token)) + { + // 认证失败:返回 401 + sendResponse(httpResponse,HttpServletResponse.SC_UNAUTHORIZED, MessageUtils.messageCustomize("systemExceptionAuthFilter10001")); + // 中断请求 + return; + } + Claims claims = JwtUtils.parseToken(token); + if (claims == null) + { + // 认证失败:返回 401 + sendResponse(httpResponse,HttpServletResponse.SC_UNAUTHORIZED,MessageUtils.messageCustomize("systemExceptionAuthFilter10002")); + // 中断请求 + return; + } + String userkey = JwtUtils.getUserKey(claims); + boolean islogin = redisService.hasKey(getTokenKey(userkey)); + if (!islogin) + { + // 认证失败:返回 401 + sendResponse(httpResponse,HttpServletResponse.SC_UNAUTHORIZED,MessageUtils.messageCustomize("systemExceptionAuthFilter10003")); + // 中断请求 + return; + } + + String userid = JwtUtils.getUserId(claims); + String username = JwtUtils.getUserName(claims); + if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) + { + // 认证失败:返回 401 + sendResponse(httpResponse,HttpServletResponse.SC_UNAUTHORIZED,MessageUtils.messageCustomize("systemExceptionAuthFilter10004")); + // 中断请求 + return; + } + } + // 拦截通过,继续执行后续过滤器或控制器 + chain.doFilter(request, response); + } + + /** + * 返回响应结构体 + * @param response + * @param code HTTP状态码 + * @param message 提示信息 + * @throws IOException + */ + private void sendResponse(HttpServletResponse response,int code, String message) throws IOException { + // 设置HTTP状态码 + response.setStatus(code); + // 设置响应内容类型(JSON格式便于前端解析) + response.setContentType("application/json;charset=UTF-8"); + // 构建响应体(包含错误信息) + String jsonResponse = String.format( + "{\"code\": "+code+", \"msg\": \"%s\"}", + message + ); + // 写入响应并关闭流 + PrintWriter writer = response.getWriter(); + writer.write(jsonResponse); + writer.flush(); + writer.close(); + } +} \ No newline at end of file diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/common/CaptchaController.java b/vetti-admin/src/main/java/com/vetti/web/controller/common/CaptchaController.java new file mode 100644 index 0000000..03618f8 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/common/CaptchaController.java @@ -0,0 +1,94 @@ +package com.vetti.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.google.code.kaptcha.Producer; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.utils.sign.Base64; +import com.vetti.common.utils.uuid.IdUtils; +import com.vetti.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/common/CommonController.java b/vetti-admin/src/main/java/com/vetti/web/controller/common/CommonController.java new file mode 100644 index 0000000..22f2291 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/common/CommonController.java @@ -0,0 +1,162 @@ +package com.vetti.web.controller.common; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.file.FileUploadUtils; +import com.vetti.common.utils.file.FileUtils; +import com.vetti.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/common") +public class CommonController +{ + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + @Autowired + private ServerConfig serverConfig; + + private static final String FILE_DELIMETER = ","; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @GetMapping("/download") + public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) + { + try + { + if (!FileUtils.checkAllowDownload(fileName)) + { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = RuoYiConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, realFileName); + FileUtils.writeBytes(filePath, response.getOutputStream()); + if (delete) + { + FileUtils.deleteFile(filePath); + } + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } + + /** + * 通用上传请求(单个) + */ + @PostMapping("/upload") + public AjaxResult uploadFile(MultipartFile file) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 通用上传请求(多个) + */ + @PostMapping("/uploads") + public AjaxResult uploadFiles(List files) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + for (MultipartFile file : files) + { + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + urls.add(url); + fileNames.add(fileName); + newFileNames.add(FileUtils.getName(fileName)); + originalFilenames.add(file.getOriginalFilename()); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @GetMapping("/download/resource") + public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) + throws Exception + { + try + { + if (!FileUtils.checkAllowDownload(resource)) + { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); + } + // 本地资源路径 + String localPath = RuoYiConfig.getProfile(); + // 数据库资源地址 + String downloadPath = localPath + FileUtils.stripPrefix(resource); + // 下载名称 + String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, downloadName); + FileUtils.writeBytes(downloadPath, response.getOutputStream()); + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/monitor/CacheController.java b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..1538655 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/CacheController.java @@ -0,0 +1,121 @@ +package com.vetti.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +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; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.domain.SysCache; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate redisTemplate; + + private final static List caches = new ArrayList(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Properties info = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.dbSize()); + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() + { + return AjaxResult.success(caches); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) + { + Set cacheKeys = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(new TreeSet<>(cacheKeys)); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) + { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue); + return AjaxResult.success(sysCache); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) + { + Collection cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) + { + redisTemplate.delete(cacheKey); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() + { + Collection cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/monitor/ServerController.java b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..793aa96 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/ServerController.java @@ -0,0 +1,27 @@ +package com.vetti.web.controller.monitor; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/server") +public class ServerController +{ + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysLogininforController.java b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..17ea2a6 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,82 @@ +package com.vetti.web.controller.monitor; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.framework.web.service.SysPasswordService; +import com.vetti.system.domain.SysLogininfor; +import com.vetti.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) + { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + util.exportExcel(response, list, "登录日志"); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) + { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysOperlogController.java b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..b77e1ff --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,69 @@ +package com.vetti.web.controller.monitor; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.system.domain.SysOperLog; +import com.vetti.system.service.ISysOperLogService; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private ISysOperLogService operLogService; + + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) + { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) + { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + util.exportExcel(response, list, "操作日志"); + } + + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable Long[] operIds) + { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return success(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysUserOnlineController.java b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..d48eea0 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,83 @@ +package com.vetti.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +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; +import com.vetti.common.annotation.Log; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.domain.SysUserOnline; +import com.vetti.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private RedisCache redisCache; + + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) + { + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List userOnlineList = new ArrayList(); + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) + { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + else if (StringUtils.isNotEmpty(ipaddr)) + { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) + { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + else + { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); + return success(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysConfigController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..6a9603a --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysConfigController.java @@ -0,0 +1,133 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.system.domain.SysConfig; +import com.vetti.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) + { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:config:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) + { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDeptController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..6025a7b --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDeptController.java @@ -0,0 +1,132 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysDept; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) + { + List depts = deptService.selectDeptList(dept); + return success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) + { + List depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) + { + if (!deptService.checkDeptNameUnique(dept)) + { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) + { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (!deptService.checkDeptNameUnique(dept)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + else if (dept.getParentId().equals(deptId)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) + { + return error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) + { + if (deptService.hasChildByDeptId(deptId)) + { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictDataController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..966ae5a --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictDataController.java @@ -0,0 +1,121 @@ +package com.vetti.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysDictData; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.system.service.ISysDictDataService; +import com.vetti.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController +{ + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) + { + startPage(); + List list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictData dictData) + { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) + { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) + { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable Long[] dictCodes) + { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictTypeController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..d79dfb0 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysDictTypeController.java @@ -0,0 +1,131 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysDictType; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) + { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictType dictType) + { + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) + { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysI18nGainController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysI18nGainController.java new file mode 100644 index 0000000..77a4a78 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysI18nGainController.java @@ -0,0 +1,38 @@ +package com.vetti.web.controller.system; + +import com.vetti.common.annotation.Anonymous; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.web.service.ISystemI18nGainService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 系统获取国际化信息 + * + * @author Wangxiangshun + */ +@Api(tags ="国际化管理") +@RestController +@RequestMapping("/system/i18nGain") +public class SysI18nGainController extends BaseController { + + @Autowired + private ISystemI18nGainService systemI18nGainService; + + /** + * 获取国际化信息 + */ + @Anonymous + @ApiOperation("查询国际化信息") + @GetMapping(value = "/getInfo") + public AjaxResult getInfo() + { + return AjaxResult.success(systemI18nGainService.getI18nInfo()); + } + +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysIndexController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..ef4acc0 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysIndexController.java @@ -0,0 +1,29 @@ +package com.vetti.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.utils.StringUtils; + +/** + * 首页 + * + * @author ruoyi + */ +@RestController +public class SysIndexController +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() + { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..097edad --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysLoginController.java @@ -0,0 +1,131 @@ +package com.vetti.web.controller.system; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysMenu; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginBody; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.text.Convert; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.web.service.SysLoginService; +import com.vetti.framework.web.service.SysPermissionService; +import com.vetti.framework.web.service.TokenService; +import com.vetti.system.service.ISysConfigService; +import com.vetti.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author ruoyi + */ +@RestController +public class SysLoginController +{ + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, token); + return ajax; + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("getInfo") + public AjaxResult getInfo() + { + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser user = loginUser.getUser(); + // 角色集合 + Set roles = permissionService.getRolePermission(user); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user); + if (!loginUser.getPermissions().equals(permissions)) + { + loginUser.setPermissions(permissions); + tokenService.refreshToken(loginUser); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); + ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } + + // 检查初始密码是否提醒修改 + public boolean initPasswordIsModify(Date pwdUpdateDate) + { + Integer initPasswordModify = Convert.toInt(configService.selectConfigByKey("sys.account.initPasswordModify")); + return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null; + } + + // 检查密码是否过期 + public boolean passwordIsExpiration(Date pwdUpdateDate) + { + Integer passwordValidateDays = Convert.toInt(configService.selectConfigByKey("sys.account.passwordValidateDays")); + if (passwordValidateDays != null && passwordValidateDays > 0) + { + if (StringUtils.isNull(pwdUpdateDate)) + { + // 如果从未修改过初始密码,直接提醒过期 + return true; + } + Date nowDate = DateUtils.getNowDate(); + return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays; + } + return false; + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysMenuController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..e7124ff --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysMenuController.java @@ -0,0 +1,142 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysMenu; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + /** + * 获取菜单列表 + */ + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:add')") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) + { + if (menuService.hasChildByMenuId(menuId)) + { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysNoticeController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..3ab55d1 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysNoticeController.java @@ -0,0 +1,91 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.system.domain.SysNotice; +import com.vetti.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) + { + startPage(); + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:add')") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) + { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:edit')") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) + { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:remove')") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable Long[] noticeIds) + { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysPostController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysPostController.java new file mode 100644 index 0000000..a39e6c7 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysPostController.java @@ -0,0 +1,129 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.system.domain.SysPost; +import com.vetti.system.service.ISysPostService; + +/** + * 岗位信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) + { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:post:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysPost post) + { + List list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + util.exportExcel(response, list, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:edit')") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) + { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysProfileController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..7aa7c12 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysProfileController.java @@ -0,0 +1,148 @@ +package com.vetti.web.controller.system; + +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.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; +import com.vetti.common.annotation.Log; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.file.FileUploadUtils; +import com.vetti.common.utils.file.FileUtils; +import com.vetti.common.utils.file.MimeTypeUtils; +import com.vetti.framework.web.service.TokenService; +import com.vetti.system.service.ISysUserService; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @GetMapping + public AjaxResult profile() + { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + return ajax; + } + + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) + { + LoginUser loginUser = getLoginUser(); + SysUser currentUser = loginUser.getUser(); + currentUser.setNickName(user.getNickName()); + currentUser.setEmail(user.getEmail()); + currentUser.setPhonenumber(user.getPhonenumber()); + currentUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) + { + return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) + { + return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserProfile(currentUser) > 0) + { + // 更新缓存用户信息 + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(@RequestBody Map params) + { + String oldPassword = params.get("oldPassword"); + String newPassword = params.get("newPassword"); + LoginUser loginUser = getLoginUser(); + Long userId = loginUser.getUserId(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) + { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return error("新密码不能与旧密码相同"); + } + newPassword = SecurityUtils.encryptPassword(newPassword); + if (userService.resetUserPwd(userId, newPassword) > 0) + { + // 更新缓存用户密码&密码最后更新时间 + loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate()); + loginUser.getUser().setPassword(newPassword); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception + { + if (!file.isEmpty()) + { + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true); + if (userService.updateUserAvatar(loginUser.getUserId(), avatar)) + { + String oldAvatar = loginUser.getUser().getAvatar(); + if (StringUtils.isNotEmpty(oldAvatar)) + { + FileUtils.deleteFile(RuoYiConfig.getProfile() + FileUtils.stripPrefix(oldAvatar)); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysRegisterController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..0188e20 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysRegisterController.java @@ -0,0 +1,38 @@ +package com.vetti.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.model.RegisterBody; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.web.service.SysRegisterService; +import com.vetti.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author ruoyi + */ +@RestController +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) + { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysRoleController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..e9194f1 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysRoleController.java @@ -0,0 +1,262 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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 com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysDept; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.framework.web.service.SysPermissionService; +import com.vetti.framework.web.service.TokenService; +import com.vetti.system.domain.SysUserRole; +import com.vetti.system.service.ISysDeptService; +import com.vetti.system.service.ISysRoleService; +import com.vetti.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) + { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:role:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysRole role) + { + List list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(SysRole.class); + util.exportExcel(response, list, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:add')") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) + { + if (!roleService.checkRoleNameUnique(role)) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (!roleService.checkRoleKeyUnique(role)) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getUsername()); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (!roleService.checkRoleNameUnique(role)) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (!roleService.checkRoleKeyUnique(role)) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) + { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) + { + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + tokenService.setLoginUser(loginUser); + } + return success(); + } + return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable Long[] roleIds) + { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) + { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) + { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) + { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/system/SysUserController.java b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysUserController.java new file mode 100644 index 0000000..8ce868d --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/system/SysUserController.java @@ -0,0 +1,266 @@ +package com.vetti.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +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; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.entity.SysDept; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.system.service.ISysDeptService; +import com.vetti.system.service.ISysPostService; +import com.vetti.system.service.ISysRoleService; +import com.vetti.system.service.ISysUserService; + +/** + * 用户信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) + { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + /** + * 获取用户列表 + */ + @GetMapping("/getList") + public AjaxResult getList(SysUser user) + { + List list = userService.selectUserList(user); + return AjaxResult.success(list); + } + + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:user:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysUser user) + { + List list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(SysUser.class); + util.exportExcel(response, list, "用户数据"); + } + + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPermi('system:user:import')") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception + { + ExcelUtil util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return success(message); + } + + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(SysUser.class); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping(value = { "/", "/{userId}" }) + public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) + { + AjaxResult ajax = AjaxResult.success(); + if (StringUtils.isNotNull(userId)) + { + userService.checkUserDataScope(userId); + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); + } + List roles = roleService.selectRoleAll(); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + ajax.put("posts", postService.selectPostAll()); + return ajax; + } + + /** + * 新增用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:add')") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysUser user) + { + deptService.checkDeptDataScope(user.getDeptId()); + roleService.checkRoleDataScope(user.getRoleIds()); + if (!userService.checkUserNameUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + deptService.checkDeptDataScope(user.getDeptId()); + roleService.checkRoleDataScope(user.getRoleIds()); + if (!userService.checkUserNameUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:remove')") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable Long[] userIds) + { + if (ArrayUtils.contains(userIds, getUserId())) + { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.checkUserDataScope(userId); + roleService.checkRoleDataScope(roleIds); + userService.insertUserAuth(userId, roleIds); + return success(); + } + + /** + * 获取部门树列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) + { + return success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/tool/SwaggerController.java b/vetti-admin/src/main/java/com/vetti/web/controller/tool/SwaggerController.java new file mode 100644 index 0000000..aacc0ad --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/tool/SwaggerController.java @@ -0,0 +1,24 @@ +package com.vetti.web.controller.tool; + +import com.vetti.common.core.controller.BaseController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * swagger 接口 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/swagger") +public class SwaggerController extends BaseController +{ + @PreAuthorize("@ss.hasPermi('tool:swagger:view')") + @GetMapping() + public String index() + { + return redirect("/doc.html"); + } +} \ No newline at end of file diff --git a/vetti-admin/src/main/java/com/vetti/web/controller/tool/TestController.java b/vetti-admin/src/main/java/com/vetti/web/controller/tool/TestController.java new file mode 100644 index 0000000..d5f51af --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/controller/tool/TestController.java @@ -0,0 +1,183 @@ +package com.vetti.web.controller.tool; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +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 com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.R; +import com.vetti.common.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; + +/** + * swagger 用户测试方法 + * + * @author ruoyi + */ +@Api("用户信息管理") +@RestController +@RequestMapping("/test/user") +public class TestController extends BaseController +{ + private final static Map users = new LinkedHashMap(); + { + users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); + users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); + } + + @ApiOperation("获取用户列表") + @GetMapping("/list") + public R> userList() + { + List userList = new ArrayList(users.values()); + return R.ok(userList); + } + + @ApiOperation("获取用户详细") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @GetMapping("/{userId}") + public R getUser(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + return R.ok(users.get(userId)); + } + else + { + return R.fail("用户不存在"); + } + } + + @ApiOperation("新增用户") + @ApiImplicitParams({ + @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class), + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class) + }) + @PostMapping("/save") + public R save(UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("更新用户") + @PutMapping("/update") + public R update(@RequestBody UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + if (users.isEmpty() || !users.containsKey(user.getUserId())) + { + return R.fail("用户不存在"); + } + users.remove(user.getUserId()); + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("删除用户信息") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @DeleteMapping("/{userId}") + public R delete(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + users.remove(userId); + return R.ok(); + } + else + { + return R.fail("用户不存在"); + } + } +} + +@ApiModel(value = "UserEntity", description = "用户实体") +class UserEntity +{ + @ApiModelProperty("用户ID") + private Integer userId; + + @ApiModelProperty("用户名称") + private String username; + + @ApiModelProperty("用户密码") + private String password; + + @ApiModelProperty("用户手机") + private String mobile; + + public UserEntity() + { + + } + + public UserEntity(Integer userId, String username, String password, String mobile) + { + this.userId = userId; + this.username = username; + this.password = password; + this.mobile = mobile; + } + + public Integer getUserId() + { + return userId; + } + + public void setUserId(Integer userId) + { + this.userId = userId; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getMobile() + { + return mobile; + } + + public void setMobile(String mobile) + { + this.mobile = mobile; + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/core/config/SwaggerConfig.java b/vetti-admin/src/main/java/com/vetti/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..f77ab78 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/core/config/SwaggerConfig.java @@ -0,0 +1,124 @@ +package com.vetti.web.core.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.vetti.common.config.RuoYiConfig; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author ruoyi + */ +@Configuration +public class SwaggerConfig +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** 是否开启swagger */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** 设置请求的统一前缀 */ + @Value("${swagger.pathMapping}") + private String pathMapping; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() + { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() + { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() + { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() + { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("Title:Vetti Dashboard API Documentation") + // 描述 + .description("Description: Explanation of all interface structures in the project") + // 作者信息 + .contact(new Contact(ruoyiConfig.getName(), null, null)) + // 版本 + .version("Version:" + ruoyiConfig.getVersion()) + .build(); + } +} diff --git a/vetti-admin/src/main/java/com/vetti/web/service/ISystemI18nGainService.java b/vetti-admin/src/main/java/com/vetti/web/service/ISystemI18nGainService.java new file mode 100644 index 0000000..8a08baf --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/service/ISystemI18nGainService.java @@ -0,0 +1,20 @@ +package com.vetti.web.service; + +import java.util.Properties; + +/** + * 国际化操作 信息 服务类 + * + * @author WangXiangShun + * @since 2025-08-27 + */ +public interface ISystemI18nGainService { + + /** + * 获取国际化数据 + * + * @return + */ + Properties getI18nInfo(); + +} diff --git a/vetti-admin/src/main/java/com/vetti/web/service/impl/SystemI18nGainServiceImpl.java b/vetti-admin/src/main/java/com/vetti/web/service/impl/SystemI18nGainServiceImpl.java new file mode 100644 index 0000000..33c5781 --- /dev/null +++ b/vetti-admin/src/main/java/com/vetti/web/service/impl/SystemI18nGainServiceImpl.java @@ -0,0 +1,48 @@ +package com.vetti.web.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.vetti.common.config.InternationalProperties; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.I18nUtil; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.web.service.ISystemI18nGainService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.Properties; + +/** + * 国际化操作 信息 服务实现类 + * + * @author WangXiangShun + * @since 2025-08-27 + */ +@Slf4j +@Service +public class SystemI18nGainServiceImpl implements ISystemI18nGainService { + + @Autowired + protected HttpServletRequest request; + + /** + * 获取国际化数据 + * + * @return + */ + @Override + public Properties getI18nInfo() { + //获取语言类型 + InternationalProperties iProperties = SpringUtils.getBean(InternationalProperties.class); + // 获取客户语言环境 + String lang = iProperties.getLang(); + if(StrUtil.isEmpty(lang)){ + throw new ServiceException(MessageUtils.messageCustomize("systemExceptionSystemI18nGainServiceImpl10001")); + } + Properties properties = I18nUtil.loadI18nProp(lang); + return properties; + } + +} diff --git a/vetti-admin/src/main/resources/META-INF/spring-devtools.properties b/vetti-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..37e7b58 --- /dev/null +++ b/vetti-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/vetti-admin/src/main/resources/application-druid.yml b/vetti-admin/src/main/resources/application-druid.yml new file mode 100644 index 0000000..6da9a04 --- /dev/null +++ b/vetti-admin/src/main/resources/application-druid.yml @@ -0,0 +1,153 @@ + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + com.vetti: debug + org.springframework: warn + pathUrl: ./logs/ + +# 用户配置 +user: + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://test.hotake.cn:13306/htk-vetti?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: Hamkke@2021 + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: ruoyi + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: Hamkke@2021 + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms +fs: + minio: + endpoint: http://test.hotake.cn:19000 # MinIO 服务地址 + access-key: minioadmin # 访问密钥(替换为你的 Access Key) + secret-key: minioadmin # 密钥(替换为你的 Secret Key) + max-file-size: 104857600 #字节(Byte) + bucket-name.: # 存储桶名称(提前在 MinIO 控制台创建) + system-fs: system-fs +# Twilio SendGrid 配置 +twilio: + sendgrid: + #你的SendGrid API Key + api-key: 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 + #已验证的发送邮箱地址 + from-email: noreply@routez.app + #你的应用名称 + from-name: RouteZ + template-ids: + routez-verification-code: d-321fee8a85704983849eb1f69313ae24 +verification: + code: + email: + # 验证码长度 + length: 5 + # 验证码过期时间(分钟) + expiration-minutes: 10 + +here-map: + api-key: q1gsOSZ899P8dVaoW2HZXq40W-AqCwB6iON5tw7-sqI + geocoding-api-url: https://geocode.search.hereapi.com/v1/geocode + reverse-geocoding-api-url: https://revgeocode.search.hereapi.com/v1/revgeocode + router-api-url: https://router.hereapi.com/v8/routes + +http: + client: + connect-timeout-seconds: 10 diff --git a/vetti-admin/src/main/resources/application.yml b/vetti-admin/src/main/resources/application.yml new file mode 100644 index 0000000..fdaa0b9 --- /dev/null +++ b/vetti-admin/src/main/resources/application.yml @@ -0,0 +1,94 @@ +# 项目相关配置 +vetti: + # 名称 + name: Vetti + # 版本 + version: 3.9.0 + # 版权年份 + copyrightYear: 2025 + # 文件路径 示例( Windows配置D:/vetti/uploadPath,Linux配置 /home/vetti/uploadPath) + profile: ./uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + # 国际化默认语言环境 + international: + enable: true + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 + +# MyBatis配置 +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.vetti.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +#################################### Swagger start ################################# +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /prod-api +#################################### Swagger end ################################### + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* + +# 安全配置 +security: + # 不校验白名单 + ignore: + whites: + - /v1/app/system/i18nGain/getInfo + - /v1/app/verificationEmail/send + - /v1/app/verificationEmail/verify + - /v1/app/auth/appLogin + - /v1/app/auth/appRegister + - /v1/app/auth/appResetPassword + - /v1/app/auth/getUserInfoByEmail + - /v1/app/verificationEmail/register/send \ No newline at end of file diff --git a/vetti-admin/src/main/resources/banner.txt b/vetti-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..64429f7 --- /dev/null +++ b/vetti-admin/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +Application Version: ${vetti.version} +Spring Boot Version: ${spring-boot.version} diff --git a/vetti-admin/src/main/resources/i18n/messages.properties b/vetti-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..c0ca2d0 --- /dev/null +++ b/vetti-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,39 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] + diff --git a/vetti-admin/src/main/resources/i18n/messages_en_US.properties b/vetti-admin/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..da788d5 --- /dev/null +++ b/vetti-admin/src/main/resources/i18n/messages_en_US.properties @@ -0,0 +1,46 @@ +#异常消息 +# system.exception.类名.编号=* Required +systemCommonTip10001 = Operation Successful + +systemExceptionSystemI18nGainServiceImpl10001 = The language type does not exist. Please try again later + +systemExceptionTokenController10001 = Illegal login information +systemExceptionTokenController10002 = Registered Successfully +systemExceptionTokenController10003 = Password Reset Successful + +systemExceptionSysAppLoginServiceImpl10001 = Email Cannot Be Empty +systemExceptionSysAppLoginServiceImpl10002 = Account Or Password Error +systemExceptionSysAppLoginServiceImpl10003 = Account Or Password Error +systemExceptionSysAppLoginServiceImpl10004 = Email Already In Use +systemExceptionSysAppLoginServiceImpl10005 = Invalid Verification Code +systemExceptionSysAppLoginServiceImpl10006 = Email Does Not Exist +systemExceptionSysAppLoginServiceImpl10007 = Passwords Do Not Match +systemExceptionSysAppLoginServiceImpl10008 = The New PassWord Cannot Match The Old PassWord +systemExceptionSysAppLoginServiceImpl10009 = The Current Logged In User Is Abnormal. Please Log In Again Or Contact Management +systemExceptionSysAppLoginServiceImpl10010 = Token has expired +systemExceptionSysAppLoginServiceImpl10011 = Invalid issuer +systemExceptionSysAppLoginServiceImpl10012 = Invalid audience +systemExceptionSysAppLoginServiceImpl10013 = Invalid token signature +systemExceptionSysAppLoginServiceImpl10014 = Failed to verify token +systemExceptionSysAppLoginServiceImpl10015 = Email unverified +systemExceptionSysAppLoginServiceImpl10016 = Invalid Google ID token + +systemExceptionAuthFilter10001 = Token Cannot Be Empty +systemExceptionAuthFilter10002 = Token Has Expired Or Verification Is Incorrect +systemExceptionAuthFilter10003 = Login Status Has Expired +systemExceptionAuthFilter10004 = Token Verification Failed + +systemVerificationEmailController10001 = The Verification Code Has Been Sent To Your Email +systemVerificationEmailController10002 = Failed To Send Verification Code, Please Try Again Later +systemVerificationEmailController10003 = Verification Code Verification Successful +systemVerificationEmailController10004 = The Verification Code Is Invalid Or Has Expired + +SystemCommandOverhaulAppController10001 = Operation Successful + +systemEmailUtil10001 = Sending Email Failed +systemR10001 = Operation Successful + +#管理端 +# manager.页面,字段 = User Manager +VerificationEmailTiTle = Your verification code +VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes. diff --git a/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties b/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..38c9487 --- /dev/null +++ b/vetti-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,47 @@ +#异常消息 +# system.exception.类名.编号=* 必须填写 +systemCommonTip10001 = 操作成功 + +systemExceptionSystemI18nGainServiceImpl10001 = 语言类型不存在,请稍后再试 + +systemExceptionTokenController10001 = 非法登录信息 +systemExceptionTokenController10002 = 注册成功 +systemExceptionTokenController10003 = 密码重置成功 + +systemExceptionSysAppLoginServiceImpl10001 = 邮箱不可为空 +systemExceptionSysAppLoginServiceImpl10002 = 账号或者密码错误 +systemExceptionSysAppLoginServiceImpl10003 = 账号或者密码错误 +systemExceptionSysAppLoginServiceImpl10004 = 邮箱已被使用 +systemExceptionSysAppLoginServiceImpl10005 = 验证码无效 +systemExceptionSysAppLoginServiceImpl10006 = 邮箱不存在 +systemExceptionSysAppLoginServiceImpl10007 = 密码不一致 +systemExceptionSysAppLoginServiceImpl10008 = 新密码不能与旧密码一致 +systemExceptionSysAppLoginServiceImpl10009 = 当前登录用户异常,请重新登录或联系管理 +systemExceptionSysAppLoginServiceImpl10010 = 令牌已过期 +systemExceptionSysAppLoginServiceImpl10011 = 颁发者无效 +systemExceptionSysAppLoginServiceImpl10012 = 无效访问群体 +systemExceptionSysAppLoginServiceImpl10013 = 令牌签名无效 +systemExceptionSysAppLoginServiceImpl10014 = 验证令牌失败 +systemExceptionSysAppLoginServiceImpl10015 = 电子邮件未验证 +systemExceptionSysAppLoginServiceImpl10016 = 无效的Google ID令牌 + +systemExceptionAuthFilter10001 = 令牌不能为空 +systemExceptionAuthFilter10002 = 令牌已过期或验证不正确 +systemExceptionAuthFilter10003 = 登录状态已过期 +systemExceptionAuthFilter10004 = 令牌验证失败 + +systemVerificationEmailController10001 = 验证码已发送到你的邮箱 +systemVerificationEmailController10002 = 发送验证码失败,请稍后重试 +systemVerificationEmailController10003 = 验证码验证成功 +systemVerificationEmailController10004 = 验证码无效或已过期 + +SystemCommandOverhaulAppController10001 = 操作成功 + +systemEmailUtil10001 = 发送邮件失败 +systemR10001 = 操作成功 + +#管理端 +# manager.页面,字段 = 用户管理 +VerificationEmailTiTle = 你的验证码 +VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。 + diff --git a/vetti-admin/src/main/resources/logback.xml b/vetti-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..ac02724 --- /dev/null +++ b/vetti-admin/src/main/resources/logback.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vetti-admin/src/main/resources/mybatis/mybatis-config.xml b/vetti-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/vetti-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/vetti-admin/target/classes/META-INF/spring-devtools.properties b/vetti-admin/target/classes/META-INF/spring-devtools.properties new file mode 100644 index 0000000..37e7b58 --- /dev/null +++ b/vetti-admin/target/classes/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/vetti-admin/target/classes/application-druid.yml b/vetti-admin/target/classes/application-druid.yml new file mode 100644 index 0000000..6da9a04 --- /dev/null +++ b/vetti-admin/target/classes/application-druid.yml @@ -0,0 +1,153 @@ + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + com.vetti: debug + org.springframework: warn + pathUrl: ./logs/ + +# 用户配置 +user: + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://test.hotake.cn:13306/htk-vetti?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: Hamkke@2021 + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: ruoyi + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: Hamkke@2021 + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms +fs: + minio: + endpoint: http://test.hotake.cn:19000 # MinIO 服务地址 + access-key: minioadmin # 访问密钥(替换为你的 Access Key) + secret-key: minioadmin # 密钥(替换为你的 Secret Key) + max-file-size: 104857600 #字节(Byte) + bucket-name.: # 存储桶名称(提前在 MinIO 控制台创建) + system-fs: system-fs +# Twilio SendGrid 配置 +twilio: + sendgrid: + #你的SendGrid API Key + api-key: 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 + #已验证的发送邮箱地址 + from-email: noreply@routez.app + #你的应用名称 + from-name: RouteZ + template-ids: + routez-verification-code: d-321fee8a85704983849eb1f69313ae24 +verification: + code: + email: + # 验证码长度 + length: 5 + # 验证码过期时间(分钟) + expiration-minutes: 10 + +here-map: + api-key: q1gsOSZ899P8dVaoW2HZXq40W-AqCwB6iON5tw7-sqI + geocoding-api-url: https://geocode.search.hereapi.com/v1/geocode + reverse-geocoding-api-url: https://revgeocode.search.hereapi.com/v1/revgeocode + router-api-url: https://router.hereapi.com/v8/routes + +http: + client: + connect-timeout-seconds: 10 diff --git a/vetti-admin/target/classes/application.yml b/vetti-admin/target/classes/application.yml new file mode 100644 index 0000000..fdaa0b9 --- /dev/null +++ b/vetti-admin/target/classes/application.yml @@ -0,0 +1,94 @@ +# 项目相关配置 +vetti: + # 名称 + name: Vetti + # 版本 + version: 3.9.0 + # 版权年份 + copyrightYear: 2025 + # 文件路径 示例( Windows配置D:/vetti/uploadPath,Linux配置 /home/vetti/uploadPath) + profile: ./uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + # 国际化默认语言环境 + international: + enable: true + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 + +# MyBatis配置 +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.vetti.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +#################################### Swagger start ################################# +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /prod-api +#################################### Swagger end ################################### + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* + +# 安全配置 +security: + # 不校验白名单 + ignore: + whites: + - /v1/app/system/i18nGain/getInfo + - /v1/app/verificationEmail/send + - /v1/app/verificationEmail/verify + - /v1/app/auth/appLogin + - /v1/app/auth/appRegister + - /v1/app/auth/appResetPassword + - /v1/app/auth/getUserInfoByEmail + - /v1/app/verificationEmail/register/send \ No newline at end of file diff --git a/vetti-admin/target/classes/banner.txt b/vetti-admin/target/classes/banner.txt new file mode 100644 index 0000000..64429f7 --- /dev/null +++ b/vetti-admin/target/classes/banner.txt @@ -0,0 +1,2 @@ +Application Version: ${vetti.version} +Spring Boot Version: ${spring-boot.version} diff --git a/vetti-admin/target/classes/i18n/messages.properties b/vetti-admin/target/classes/i18n/messages.properties new file mode 100644 index 0000000..c0ca2d0 --- /dev/null +++ b/vetti-admin/target/classes/i18n/messages.properties @@ -0,0 +1,39 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] + diff --git a/vetti-admin/target/classes/i18n/messages_en_US.properties b/vetti-admin/target/classes/i18n/messages_en_US.properties new file mode 100644 index 0000000..35f2dbf --- /dev/null +++ b/vetti-admin/target/classes/i18n/messages_en_US.properties @@ -0,0 +1,496 @@ +#异常消息 +# system.exception.类名.编号=* Required +systemCommonTip10001 = Operation Successful + +systemExceptionSystemI18nGainServiceImpl10001 = The language type does not exist. Please try again later + +systemExceptionTokenController10001 = Illegal login information +systemExceptionTokenController10002 = Registered Successfully +systemExceptionTokenController10003 = Password Reset Successful + +systemExceptionSysAppLoginServiceImpl10001 = Email Cannot Be Empty +systemExceptionSysAppLoginServiceImpl10002 = Account Or Password Error +systemExceptionSysAppLoginServiceImpl10003 = Account Or Password Error +systemExceptionSysAppLoginServiceImpl10004 = Email Already In Use +systemExceptionSysAppLoginServiceImpl10005 = Invalid Verification Code +systemExceptionSysAppLoginServiceImpl10006 = Email Does Not Exist +systemExceptionSysAppLoginServiceImpl10007 = Passwords Do Not Match +systemExceptionSysAppLoginServiceImpl10008 = The New PassWord Cannot Match The Old PassWord +systemExceptionSysAppLoginServiceImpl10009 = The Current Logged In User Is Abnormal. Please Log In Again Or Contact Management +systemExceptionSysAppLoginServiceImpl10010 = Token has expired +systemExceptionSysAppLoginServiceImpl10011 = Invalid issuer +systemExceptionSysAppLoginServiceImpl10012 = Invalid audience +systemExceptionSysAppLoginServiceImpl10013 = Invalid token signature +systemExceptionSysAppLoginServiceImpl10014 = Failed to verify token +systemExceptionSysAppLoginServiceImpl10015 = Email unverified +systemExceptionSysAppLoginServiceImpl10016 = Invalid Google ID token + +systemExceptionAuthFilter10001 = Token Cannot Be Empty +systemExceptionAuthFilter10002 = Token Has Expired Or Verification Is Incorrect +systemExceptionAuthFilter10003 = Login Status Has Expired +systemExceptionAuthFilter10004 = Token Verification Failed + +systemVerificationEmailController10001 = The Verification Code Has Been Sent To Your Email +systemVerificationEmailController10002 = Failed To Send Verification Code, Please Try Again Later +systemVerificationEmailController10003 = Verification Code Verification Successful +systemVerificationEmailController10004 = The Verification Code Is Invalid Or Has Expired + +SystemCommandOverhaulAppController10001 = Operation Successful + +systemEmailUtil10001 = Sending Email Failed +systemR10001 = Operation Successful + +#管理端 +# manager.页面,字段 = User Manager +VerificationEmailTiTle = Your verification code +VerificationEmailContent = Your verification code is: {0}, valid for {1} minutes. + +#APP端 +AppSystemPassWordTip10001 = Two Inputs Are Inconsistent +AppSystemPassWordTip10002 = Cannot Include Your Name Or Email +AppSystemPassWordTip10003 = Contains At Least 8 Characters +AppSystemPassWordTip10004 = Contains Symbols Or Numbers + +OnboardingOnboarding2ActionSkip = Skip +OnboardingOnboarding2TitleOne = Smart, fast routes every time. +OnboardingOnboarding2InfoOne = Optimised Navigation to get you there faster and safer. +OnboardingOnboarding2ButtonGetStarted = Get Started +OnboardingOnboarding3ActionSkip = Skip +OnboardingOnboarding3TitleOne = Stay compliant, drive easy. +OnboardingOnboarding3InfoOne = Seamless EWD & CoR compliance to keep you on track. +OnboardingOnboarding3ButtonGetStarted = Get Started +OnboardingGetStartedTitleOne = Maximise earnings, minimise hassle. +OnboardingGetStartedInfoOne = Sign up or login to see what happening near you +OnboardingGetStartedButtonEamil = Continue with Email +OnboardingGetStartedButtonGoogle = Continue with Google +OnboardingGetStartedButtonApple = Continue with Apple +AuthenticationLoginEmptyStateTitleOne = Sign in to Routez +AuthenticationLoginEmptyStateInfoOne = Welcome back! Please enter yout details. +AuthenticationLoginEmptyStatePlaceholderEamil = Email +AuthenticationLoginEmptyStatePlaceholderPassword = Password +AuthenticationLoginEmptyStateInfoTwo = Forgot password? +AuthenticationLoginEmptyStateActionReset = Reset it +AuthenticationLoginEmptyStateButtonSignin = Sign in +AuthenticationLoginEmptyStateButtonGoogle = Sign in with Google +AuthenticationLoginEmptyStateButtonApple = Sign in with Apple +AuthenticationLoginEmptyStateInfoThree = Don’t have and account? +AuthenticationLoginEmptyStateActionSignup = Sign Up +AuthenticationSignUpEmptyStateTitleOne = Sign Up +AuthenticationSignUpEmptyStatePlaceholderFullname = Fullname +AuthenticationSignUpEmptyStatePlaceholderEamil = Email +AuthenticationSignUpEmptyStatePlaceholderPassword = Password +AuthenticationSignUpEmptyStatePlaceholderFullnameTips = Please Check Fullname +AuthenticationSignUpEmptyStatePlaceholderEamilTips = Please Check Email +AuthenticationSignUpEmptyStatePlaceholderPasswordTips = Please Check Password +AuthenticationSignUpEmptyStateInfoOne = By signing up, you agree to our +AuthenticationSignUpEmptyStateLegalService = Terms of Service +AuthenticationSignUpEmptyStateInfoTwo = and +AuthenticationSignUpEmptyStateLegalpolicy = Privacy Policy. +AuthenticationSignUpEmptyStateButtonSignup = Sign Up +AuthenticationSignUpEmptyStateButtonGoogle = Sign in with Google +AuthenticationSignUpEmptyStateButtonApple = Sign in with Apple +AuthenticationSignUpEmptyStateInfoThree = Already have and account? +AuthenticationSignUpEmptyStateActionSignin = Sign in +AuthenticationEnterVerificationCodeTitleOne = Almost there! +AuthenticationEnterVerificationCodeInfoOne = Check your email inbox and input the verification code to verify your account. +AuthenticationEnterVerificationCodeButtonContinue = Continue +AuthenticationEnterVerificationCodeButtonResend = Resend Code +AuthenticationForgotPasswordTitleOne = Can’t sign in? +AuthenticationForgotPasswordInfoOne = Enter the email associated with your account, and Carline will send you a link to reset your password. +AuthenticationForgotPasswordPlaceholderEamil = Email +AuthenticationForgotPasswordButtonReset = Reset Password +AuthenticationForgotPasswordButtonReturn = Return to Sign In +AuthenticationEnterNewPasswordTitleOne = Create your new password +AuthenticationEnterNewPasswordInfoOne = Your new password must be different from previous password. +AuthenticationEnterNewPasswordPlaceholderPassword = Password +AuthenticationEnterNewPasswordPlaceholderRepeat = Password +AuthenticationEnterNewPasswordAlertOne = Must not contain your name or email +AuthenticationEnterNewPasswordAlertTwo = At least 8 characters +AuthenticationEnterNewPasswordAlertThree = Contains a symbol or a number +AuthenticationEnterNewPasswordButtonCreate = Create New Password +DriverMyProfileTitleOne = Profile +DriverMyProfileButtonMyProfile = My Profile +DriverMyProfileTitleTwo = General +DriverMyProfileActionOne = Appointment +DriverMyProfileActionTwo = Test Drive +DriverMyProfileActionThree = My vouchers +DriverMyProfileTitleThree = Browse history +DriverMyProfileNavMome = Home +DriverMyProfileNavFavourite = Map +DriverMyProfileNavMessage = Fleet +DriverMyProfileNavProfile = Profile +DriverPreTripCheckTitleOne = Pre-Trip Check +DriverPreTripCheckTitleTwo = Next Service +DriverPreTripCheckButtonStart = Start Check +DriverPreTripCheckButtonOne = Check DOne +DriverPreTripCheckButtonFinish = Finish Check +DriverPreTripCheckButtonStartfull = Start Full Inspection +DriverPreTripCheckListTitleOne = Pre-Trip Inspection +DriverPreTripCheckListInfoOne = Complete +DriverPreTripCheckDetailInfoOne = Inspection Point +DriverPreTripCheckDetailTitleOne = Required Specifications +DriverPreTripCheckDetailButtonFail = Fail +DriverPreTripCheckDetailButtonPass = Pass +DriverPreTripCheckDetailLabelOne = Issue Details +DriverPreTripCheckDetailTitleTwo = Photos +DriverPreTripCheckDetailButtonAdd = Add Photo +DriverPreTripCheckDetailButtonSubmit = Submit +DriverPreTripCheckHistoryTitleOne = Hours of Service +DriverPreTripCheckHistoryInfoOne = Last Break +DriverPreTripCheckHistoryInfoTwo = Next Required +DriverPreTripCheckHistoryTitleTwo = Current Route +DriverPreTripCheckHistoryTitleThree = Current Vehicle +DriverPreTripCheckHistoryButtonStartInspection = Start Inspection +DriverPreTripCheckHistoryButtonViewDetails = View Details +DriverPreTripCheckHistoryTitleFour = Recent Inspections +DriverPreTripCheckHistoryStatusIssues = issues found +DriverPreTripCheckHistoryButtonView = View +DriverJobCenterTitleOne = Truck +DriverJobCenterStatusCompleted = Completed +DriverJobCenterStatusReamining = Remaining +DriverJobCenterTitleTwo = Next Job +DriverJobCenterInfoPackages = packages +DriverJobCenterButtonDeliverTo = Deliver to Main Dock +DriverJobCenterTitleThree = Upcoming Stops +DriverJobCenterButtonSelect = Select Job +DriverCurrentDeliveryTitleOne = Current Delivery +DriverCurrentDeliveryTitleTwo = Packages +DriverCurrentDeliveryTitleThree = Delivery Notes +DriverCurrentDeliveryButtonStartNav = Start Navigation +DriverCurrentDeliveryCompleteTitleOne = Current Delivery +DriverCurrentDeliveryCompleteTitleTwo = Packages +DriverCurrentDeliveryCompleteTitleThree = Delivery Notes +DriverCurrentDeliveryCompleteButtonComplete = Complete Delivery +TruckTruckParameterTitleOne = Truck details +TruckTruckParameterTitleTwo = Load Capacity +TruckTruckParameterTitleThree = Condition +TruckTruckParameterButtonAdd = Add to Fleet +TruckVehicleProfileTitleOne = Add My Vehicles +TruckVehicleProfileInfoOne = The selected vehicles will be used for route calculation and guidance. +TruckVehicleProfileTitleTwo = Trucks +TruckVehicleProfileTitleThree = Passenger Cars +TruckVehicleProfileButtonAdd = Add Vehicle +# 新增加 +NavigationReferenceNavHomePlaceholderSearch = Where to? +NavigationReferenceNavHomeButtonHome = home +NavigationReferenceNavHomeButtonDepo = depo +NavigationReferenceNavHomeButtonSaved = saved + +NavigationReferenceNavDockUpPlaceholderSearch = Where to? +NavigationReferenceNavDockUpButtonHome = home +NavigationReferenceNavDockUpButtonDepo = depo +NavigationReferenceNavDockUpButtonAddPlace = add place +NavigationReferenceNavDockUpTitleSaved = Saved Location +NavigationReferenceNavDockUpActionSeeAll = See all +NavigationReferenceNavDockUpButtonAddJobLocation = Add Job Location + +NavigationReferenceNavSearchNormalPlaceholderSearch = Where to? +NavigationReferenceNavSearchNormalActionCancel = Cancel +NavigationReferenceNavSearchNormalButtonRestaurant = Restaurant +NavigationReferenceNavSearchNormalButtonCoffee = Coffee +NavigationReferenceNavSearchNormalButtonATM = ATM +NavigationReferenceNavSearchNormalButtonShopping = Shopping +NavigationReferenceNavSearchNormalButtonFuel = Fuel +NavigationReferenceNavSearchNormalTitleRecentSearches = RECENT SEARCHES +NavigationReferenceNavSearchNormalActionClear = Clear + +NavigationReferenceNavSearchSelectPlaceholderYourLocation = Your Location +NavigationReferenceNavSearchSelectButtonTruck = Truck +NavigationReferenceNavSearchSelectButtonVan = Van +NavigationReferenceNavSearchSelectButtonCar = Car +NavigationReferenceNavSearchSelectTitleLeavingNow = Leaving Now +NavigationReferenceNavSearchSelectActionRoutePreferences = Route Preferences + +NavigationReferenceNavRoutePreferenceTitleRoutePreferences = Route Preferences +NavigationReferenceNavRoutePreferenceinfoCar = Car +NavigationReferenceNavRoutePreferenceToggleSetMaxVehicleSpeed = Set max vehicle speed +NavigationReferenceNavRoutePreferenceInfoOne = This speed will be used to calculate travel time and alert you in case of over speeding +NavigationReferenceNavRoutePreferenceTitleZoneRegulations = ZONE REGULATIONS +NavigationReferenceNavRoutePreferenceMenuLicensePlateNumber = License plate number +NavigationReferenceNavRoutePreferenceInfoTwo = Indonesia and the Philippines +NavigationReferenceNavRoutePreferenceTitleAVOID = AVOID +NavigationReferenceNavRoutePreferenceCheckboxAvoidUTurns = Avoid U-turns +NavigationReferenceNavRoutePreferenceCheckboxAvoidFerries = Avoid ferries +NavigationReferenceNavRoutePreferenceCheckboxAvoidHighways = Avoid highways +NavigationReferenceNavRoutePreferenceCheckboxAvoidTunnels = Avoid tunnels +NavigationReferenceNavRoutePreferenceCheckboxAvoidTollRoads = Avoid toll roads +NavigationReferenceNavRoutePreferenceCheckboxAvoidUnpavedRoads = Avoid unpaved roads + +NavigationReferenceNavigatingTitleArrival = arrival +NavigationReferenceNavigatingTitleRemaining = remaining +NavigationReferenceNavigatingTitleDistance = distance + +NavigationReferenceNavOverviewTitleArrival = arrival +NavigationReferenceNavOverviewTitleAddAStop = ADD A STOP +NavigationReferenceNavSearchSelectButtonSearch = Search +NavigationReferenceNavSearchSelectButtonGas = Gas +NavigationReferenceNavSearchSelectButtonParking = Parking +NavigationReferenceNavSearchSelectButtonRestaurants = Restaurants +NavigationReferenceNavOverviewTitleQuickSettings = QUICK SETTINGS +NavigationReferenceNavOverviewToggleSpeedAlert = Speed alert +NavigationReferenceNavOverviewToggleOffline = Offline +NavigationReferenceNavOverviewToggleTheme = Theme +NavigationReferenceNavOverviewButtonMoreSetting = More setting +NavigationReferenceNavOverviewTitleOverview = OVERVIEW + +NavigationReferenceHomePageMenuStartDailyRoute = Start Daily Route +NavigationReferenceHomePageMenuCurrentRoute = Current Route +NavigationReferenceHomePageInfoNoActiveRoute = No active route +NavigationReferenceHomePageMenuMyDiary = My Diary / Log +NavigationReferenceHomePageInfoElectronicWorkDiary = Electronic work diary +NavigationReferenceHomePageMenuTripsReports = Trips & Reports +NavigationReferenceHomePageInfoViewTripHistory = View trip history +NavigationReferenceHomePageMenuDefectsMaintenance = Defects & Maintenance +NavigationReferenceHomePageInfoVehicleIssues = Vehicle issues + +NavigationReferenceSetVehicleInfoTitleDimensionsWeight = Dimensions & Weight +NavigationReferenceSetVehicleInfoTitleVehicleSpecifications = Vehicle Specifications +NavigationReferenceSetVehicleInfoinfoOne = Enter total size and weight including trailersand load +NavigationReferenceSetVehicleInfoTitlePhysicalDimensions = Physical Dimensions +NavigationReferenceSetVehicleInfoTitleUnitM = m +NavigationReferenceSetVehicleInfoTitleUnitCM = cm +NavigationReferenceSetVehicleInfoTitleHeight = Height +NavigationReferenceSetVehicleInfoTitleWidth = Width +NavigationReferenceSetVehicleInfoTitleLength = Length +NavigationReferenceSetVehicleInfoTitleWeightSpecifications = Weight Specifications +NavigationReferenceSetVehicleInfoTitleTotalWeight = Total Weight +NavigationReferenceSetVehicleInfoTitleUnitTonnes = tonnes +NavigationReferenceSetVehicleInfoInfoTwo = 15 metric tons =33,069 lbs +NavigationReferenceSetVehicleInfoButtonNext = Next: Hazardous Materials + +NavigationReferenceHazardousMaterialsTitleHazardousMaterials = Hazardous Materials +NavigationReferenceHazardousMaterialsTitleCargoClassification = Cargo Classification +NavigationReferenceHazardousMaterialsInfoOne = Select any hazardous materials you will betransporting +NavigationReferenceHazardousMaterialsTitleHazmatCategories = Hazmat Categories +NavigationReferenceHazardousMaterialsToggleExplosives = Explosives +NavigationReferenceHazardousMaterialsToggleGas = Gas +NavigationReferenceHazardousMaterialsToggleFlammable = Flammable +NavigationReferenceHazardousMaterialsTogglePoison = Poison +NavigationReferenceHazardousMaterialsToggleRadioactive = Radioactive +NavigationReferenceHazardousMaterialsToggleCorrosive = Corrosive +NavigationReferenceHazardousMaterialsToggleMiscellaneous = Miscellaneous +NavigationReferenceHazardousMaterialsButtonNext = Next: Tunnel Category + +NavigationReferenceTripSummaryTitleTripSummary = Trip Summary +NavigationReferenceTripSummaryTitleTripCompletedSuccessfully = Trip Completed Successfully +NavigationReferenceTripSummaryInfoOne = All compliance requirements met +NavigationReferenceTripSummaryTitleTripStatistics = Trip Statistics +NavigationReferenceTripSummaryTitleStartTime = START TIME +NavigationReferenceTripSummaryTitleEndTime = END TIME +NavigationReferenceTripSummaryTitleDistance = DISTANCE +NavigationReferenceTripSummaryTitleDuration = DURATION +NavigationReferenceTripSummaryTitleComplianceStatus = Compliance Status +NavigationReferenceTripSummaryStatusOne = Pre-trip inspection completed +NavigationReferenceTripSummaryStatusTwo = Route configured properly +NavigationReferenceTripSummaryStatusThree = Break requirements met +NavigationReferenceTripSummaryStatusFour = No incidents reported +NavigationReferenceTripSummaryButtonPDF = PDF +NavigationReferenceTripSummaryButtonDone = Done + +TripReportAndProfileDefectsMaintenanceButtonBack = Back +TripReportAndProfileDefectsMaintenanceTitleDefectsMaintenance = Defects & Maintenance +TripReportAndProfileDefectsMaintenanceTabOpen = Open +TripReportAndProfileDefectsMaintenanceTabResolved = Resolved +TripReportAndProfileDefectsMaintenanceTitleReported = Reported +TripReportAndProfileDefectsMaintenanceTitleLoaction = Loaction +TripReportAndProfileDefectsMaintenanceTitleReporter = Reporter +TripReportAndProfileDefectsMaintenanceButtonAddNewDefect = Add New Defect + +TripReportAndProfileMyDiaryLogButtonBack = Back +TripReportAndProfileMyDiaryLogTitleMyDiaryLog = My Diary / Log +TripReportAndProfileMyDiaryLogTabToday = Today +TripReportAndProfileMyDiaryLogTabWeek = Week +TripReportAndProfileMyDiaryLogTab28Days = 28Days +TripReportAndProfileMyDiaryLogTitleDailyTotals = Daily Totals +TripReportAndProfileMyDiaryLogTitleDrivingTime = Driving Time +TripReportAndProfileMyDiaryLogTitleOnDutyTime = On Duty Time +TripReportAndProfileMyDiaryLogTitleOffDutyTime = Off Duty Time +TripReportAndProfileMyDiaryLogTitleTotalBreaks = Total Breaks +TripReportAndProfileMyDiaryLogStatusRestDay = Rest Day +TripReportAndProfileMyDiaryLogTitleOffDuty = Off Duty +TripReportAndProfileMyDiaryLogTitleDriving = Driving +TripReportAndProfileMyDiaryLogTitleOnDuty = OnDuty +TripReportAndProfileMyDiaryLogStatusCompliant = Compliant +TripReportAndProfileMyDiaryLogStatusViolation = Violation +TripReportAndProfileMyDiaryLogTitle28DayComplianceOverview = 28-Day Compliance Overview +TripReportAndProfileMyDiaryLogStatusOff = Off +TripReportAndProfileMyDiaryLogButtonExport = Export +TripReportAndProfileMyDiaryLogButtonEmailReport = Email Report + +TripReportAndProfileTripsReportsButtonBack = Back +TripReportAndProfileTripsReportsTitleTripsReports = Trips & Reports +TripReportAndProfileTripsReportsTabRecent = Recent +TripReportAndProfileTripsReportsTabThisWeek = This Week +TripReportAndProfileTripsReportsTabThisMonth = This Month +TripReportAndProfileTripsReportsTitleTotalTrips = TOTAL TRIPS +TripReportAndProfileTripsReportsTitleCompliance = COMPLIANCE +TripReportAndProfileTripsReportsTitleDistance = Distance +TripReportAndProfileTripsReportsTitleDuration = Duration +TripReportAndProfileTripsReportsTitleLoadType = Load Type +TripReportAndProfileTripsReportsActionViewDetails = View Details +TripReportAndProfileTripsReportsButtonExportAl = Export Al +TripReportAndProfileTripsReportsButtonFilter = Filter + +TripReportAndProfileMyProfileDetailButtonBack = Back +TripReportAndProfileMyProfileDetailTitleProfile = Profile +TripReportAndProfileMyProfileDetailActionEdit = Edit +TripReportAndProfileMyProfileDetailTitleDriverInformation = Driver Information +TripReportAndProfileMyProfileDetailTitleLicenceNumber = Licence Number +TripReportAndProfileMyProfileDetailTitleState = State +TripReportAndProfileMyProfileDetailTitleExpiryDate = Expiry Date +TripReportAndProfileMyProfileDetailTitleABN = ABN +TripReportAndProfileMyProfileDetailTitleMyVehicles = My Vehicles +TripReportAndProfileMyProfileDetailTitleAccountSettings = Account Settings +TripReportAndProfileMyProfileDetailTitlePushNotifications = Push Notifications +TripReportAndProfileMyProfileDetailTitleLocationServices = Location Services +TripReportAndProfileMyProfileDetailTitleAutoBackup = Auto Backup +TripReportAndProfileMyProfileDetailButtonSettings = Settings +TripReportAndProfileMyProfileDetailButtonSign Out = Sign Out + +TripReportAndProfileTripInProgressTitleTripInProgress = Trip in Progress +TripReportAndProfileTripInProgressActionActive = Active +TripReportAndProfileTripInProgressTitleCurrentLocation = Current Location +TripReportAndProfileTripInProgressTitleDestination = Destination +TripReportAndProfileTripInProgressTitleTripDuration = Trip Duration +TripReportAndProfileTripInProgressTitleWorkTime = Work Time +TripReportAndProfileTripInProgressTitleDriveTime = Drive Time +TripReportAndProfileTripInProgressTitleRestTaken = Rest Taken +TripReportAndProfileTripInProgressTitleQuickActions = Quick Actions +TripReportAndProfileTripInProgressButtonTakeBreak = Take Break +TripReportAndProfileTripInProgressButtonReportlssue = Report lssue +TripReportAndProfileTripInProgressButtonFuelStop = Fuel Stop +TripReportAndProfileTripInProgressButtonAddNote = Add Note +TripReportAndProfileTripInProgressButtonEndTrip = End Trip + +TripReportAndProfileEndTripButtonBack = Back +TripReportAndProfileEndTripTitleEndTrip = End Trip +TripReportAndProfileEndTripTitleTripSummary = Trip Summary +TripReportAndProfileEndTripTitleStartLocation = Start Location +TripReportAndProfileEndTripTitleEndLocation = End Location +TripReportAndProfileEndTripTitleTotalDistance = Total Distance +TripReportAndProfileEndTripTitleTripDuration = Trip Duration +TripReportAndProfileEndTripTitleFuelUsed = Fuel Used (Est.) +TripReportAndProfileEndTripTitleFinalOdometerLu = Final OdometerLu +TripReportAndProfileEndTripTitleStartOdometer = Start Odometer +TripReportAndProfileEndTripTitleEndOdometer = End Odometer +TripReportAndProfileEndTripTitleDeliveryConfirmation = Delivery Confirmation +TripReportAndProfileEndTripTitleCargoDeliveredSuccessfully = Cargo delivered successfully +TripReportAndProfileEndTripTitleDeliveryDocumentationComplete = Delivery documentation complete +TripReportAndProfileEndTripTitleVehicleParkedSafely = Vehicle parked safely +TripReportAndProfileEndTripPlaceholderAddTripNotes = Add trip notes (optional) +TripReportAndProfileEndTripButtonCompleteTrip = Complete Trip + +TripReportAndProfileBreakRestButtonBack = Back +TripReportAndProfileBreakRestTitleBreakRest = Break & Rest +TripReportAndProfileBreakRestTitleBreakTimer = Break Timer +TripReportAndProfileBreakRestInfoOne = Minimum +TripReportAndProfileBreakRestInfoTwo = required +TripReportAndProfileBreakRestTitleWorkTimeSummary = Work Time Summary +TripReportAndProfileBreakRestTitleTotalWorkTime = Total Work Time +TripReportAndProfileBreakRestTitleContinuousDriving = Continuous Driving +TripReportAndProfileBreakRestTitleLastBreak = Last Break +TripReportAndProfileBreakRestTitleTotalBreaksToday = Total Breaks Today +TripReportAndProfileBreakRestTitleComplianceStatus = Compliance Status +TripReportAndProfileBreakRestTitleDailyWorkLimit = Daily Work Limit +TripReportAndProfileBreakRestTitleWeeklyHours = Weekly Hours +TripReportAndProfileBreakRestButtonEndBreakResume = End Break & Resume + + +TripReportAndProfileTripCompleteTitleTripComplete = Trip Complete! +TripReportAndProfileTripCompleteInfoOne = Your trip has been successfully logged and saved to your electronic work diary. +TripReportAndProfileTripCompleteTitleTripStatistics = Trip Statistics +TripReportAndProfileTripCompleteTitleDistance = Distance +TripReportAndProfileTripCompleteTitleDuration = Duration +TripReportAndProfileTripCompleteTitleAvgSpeed = Avg Speed +TripReportAndProfileTripCompleteTitleFuelUsed = Fuel Used +TripReportAndProfileTripCompleteTitleAllComplianceMet = All Compliance Met +TripReportAndProfileTripCompleteInfoTwo = Your trip complies with all NHVR regulations +TripReportAndProfileTripCompleteButtonViewSummary = View Summary +TripReportAndProfileTripCompleteButtonBackToDashboard = Back to Dashboard + +TripReportAndProfileReportIncidentButtonBack = Back +TripReportAndProfileReportIncidentTitleReportIncident = Report Incident +TripReportAndProfileReportIncidentStatusUrgent = URGENT +TripReportAndProfileReportIncidentTitleIncidentType = Incident Type +TripReportAndProfileReportIncidentTitleLocationDetails = Location Details +TripReportAndProfileReportIncidentTitleCurrentLocation = Current Location +TripReportAndProfileReportIncidentPlaceholderOne = Nearest landmark or address +TripReportAndProfileReportIncidentTitleIncidentDetails = Incident Details +TripReportAndProfileReportIncidentPlaceholderTwo = Describe what happened.. +TripReportAndProfileReportIncidentTitleAnyInjuries = Any injuries? +TripReportAndProfileReportIncidentButtonYes = Yes +TripReportAndProfileReportIncidentButtonNo = No +TripReportAndProfileReportIncidentTitleEmergencyServicesCalled = Emergency services called? +TripReportAndProfileReportIncidentTitlePhotosEvidence = Photos & Evidence +TripReportAndProfileReportIncidentButtonSubmitReport = Submit Report +TripReportAndProfileReportIncidentButtonSaveDraft = Save Draft + +TripReportAndProfileDailySummaryButtonBack = Back +TripReportAndProfileDailySummaryTitleDailySummary = Daily Summary +TripReportAndProfileDailySummaryInfoOne = Work Day Complete +TripReportAndProfileDailySummaryTitleTodayPerformance = Today's Performance +TripReportAndProfileDailySummaryTitleTotalDistance = Total Distance +TripReportAndProfileDailySummaryTitleTotalWorkTime = Total work Time +TripReportAndProfileDailySummaryTitleTripsCompleted = Trips Completed +TripReportAndProfileDailySummaryTitleTotalBreaks = Total Breaks +TripReportAndProfileDailySummaryTitleTripDetails = Trip Details +TripReportAndProfileDailySummaryTitleTrip = Trip +TripReportAndProfileDailySummaryTitleComplianceSummary = Compliance Summary +TripReportAndProfileDailySummaryTitleWorkTimeLimits = Work Time Limits +TripReportAndProfileDailySummaryTitleRestBreaks = Rest Breaks +TripReportAndProfileDailySummaryTitleElectronicWorkDiary = Electronic Work Diary +TripReportAndProfileDailySummaryButtonExportReport = Export Report +TripReportAndProfileDailySummaryButtonSignOffDay = Sign Off Day + +AuthenticationAddDriverTitleAddVehicle = Add Vehicle +AuthenticationAddDriverTitleVehicleInformation = Vehicle Information +AuthenticationAddDriverTagRegistration = REGISTRATION +AuthenticationAddDriverTagVIN = VIN(VEHICLEIDENTIFICATIONNUMBER) +AuthenticationAddDriverTagRegistrationExpiry = REGISTRATION EXPIRY +AuthenticationAddDriverTagInsuranceExpiry = INSURANCE EXPIRY +AuthenticationAddDriverTagVehicleType = VEHICLE TYPE +AuthenticationAddDriverOptionSelectType = Select type +AuthenticationAddDriverTagMake = MAKE +AuthenticationAddDriverTagModel = MODEL +AuthenticationAddDriverTagYear = YEAR +AuthenticationAddDriverTagColor = COLOR +AuthenticationAddDriverTagDimensionsCapacity = Dimensions & Capacity +AuthenticationAddDriverTagVehicleHeight = Vehicle Height +AuthenticationAddDriverTagVehicleWidth = Vehicle Width +AuthenticationAddDriverTagVehicleLength = Vehicle Length +AuthenticationAddDriverTagGrossWeight = Gross Weight +AuthenticationAddDriverTitleAdditionalSpecifications = Additional Specifications +AuthenticationAddDriverTagAxles = AXLES +AuthenticationAddDriverTagTrailers = TRAILERS +AuthenticationAddDriverTagWeightPerAxle = WEIGHT PER AXLE +AuthenticationAddDriverTagTruckType = TRUCK TYPE +AuthenticationAddDriverTitleHazardousMaterials = Hazardous Materials +AuthenticationAddDriverTagCarryingHazardousGoods = CARRYING HAZARDOUS GOODS? +AuthenticationAddDriverButtonNo = No +AuthenticationAddDriverButtonYes = Yes +AuthenticationAddDriverButtonAddVehicle = Add Vehicle + +AuthenticationAddVehicleTitleDriverProfile = Driver Profile +AuthenticationAddVehicleTitlePersonalInformation = Personal Information +AuthenticationAddVehicleTagFirstName = FIRST NAME +AuthenticationAddVehicleTagLastName = LAST NAME +AuthenticationAddVehicleTagPhoneNumber = PHONE NUMBER +AuthenticationAddVehicleTitleLicenseCertification = License & Certification +AuthenticationAddVehicleTagDriverLicenseNumber = DRIVER'S LICENSE NUMBER +AuthenticationAddVehicleTagLicenseClass = LICENSE CLASS +AuthenticationAddVehicleTagLicenseState = LICENSE STATE +AuthenticationAddVehicleOptionSelect = Select +AuthenticationAddVehicleTagLicenseExpiry = LICENSE EXPIRY +AuthenticationAddVehicleTagMedicalCert = MEDICAL CERT +AuthenticationAddVehicleTitleHazmatCertification = Hazmat Certification +AuthenticationAddVehicleTagHazmatCertified = HAZMAT CERTIFIED? +AuthenticationAddVehicleButtonNo = No +AuthenticationAddVehicleButtonYes = Yes +AuthenticationAddVehicleButtonSave = Save + +AuthenticationHazmatDialogTitleHazmatCertification = Hazmat Certification +AuthenticationHazmatDialogTagHazmatCertified = HAZMAT CERTIFIED? +AuthenticationHazmatDialogButtonNo = No +AuthenticationHazmatDialogButtonYes = Yes +AuthenticationHazmatDialogTagHazmatExpiryDate = HAZMAT EXPIRY DATE +AuthenticationHazmatDialogOptionSelect = Select date \ No newline at end of file diff --git a/vetti-admin/target/classes/i18n/messages_zh_CN.properties b/vetti-admin/target/classes/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..4cf3dd6 --- /dev/null +++ b/vetti-admin/target/classes/i18n/messages_zh_CN.properties @@ -0,0 +1,498 @@ +#异常消息 +# system.exception.类名.编号=* 必须填写 +systemCommonTip10001 = 操作成功 + +systemExceptionSystemI18nGainServiceImpl10001 = 语言类型不存在,请稍后再试 + +systemExceptionTokenController10001 = 非法登录信息 +systemExceptionTokenController10002 = 注册成功 +systemExceptionTokenController10003 = 密码重置成功 + +systemExceptionSysAppLoginServiceImpl10001 = 邮箱不可为空 +systemExceptionSysAppLoginServiceImpl10002 = 账号或者密码错误 +systemExceptionSysAppLoginServiceImpl10003 = 账号或者密码错误 +systemExceptionSysAppLoginServiceImpl10004 = 邮箱已被使用 +systemExceptionSysAppLoginServiceImpl10005 = 验证码无效 +systemExceptionSysAppLoginServiceImpl10006 = 邮箱不存在 +systemExceptionSysAppLoginServiceImpl10007 = 密码不一致 +systemExceptionSysAppLoginServiceImpl10008 = 新密码不能与旧密码一致 +systemExceptionSysAppLoginServiceImpl10009 = 当前登录用户异常,请重新登录或联系管理 +systemExceptionSysAppLoginServiceImpl10010 = 令牌已过期 +systemExceptionSysAppLoginServiceImpl10011 = 颁发者无效 +systemExceptionSysAppLoginServiceImpl10012 = 无效访问群体 +systemExceptionSysAppLoginServiceImpl10013 = 令牌签名无效 +systemExceptionSysAppLoginServiceImpl10014 = 验证令牌失败 +systemExceptionSysAppLoginServiceImpl10015 = 电子邮件未验证 +systemExceptionSysAppLoginServiceImpl10016 = 无效的Google ID令牌 + +systemExceptionAuthFilter10001 = 令牌不能为空 +systemExceptionAuthFilter10002 = 令牌已过期或验证不正确 +systemExceptionAuthFilter10003 = 登录状态已过期 +systemExceptionAuthFilter10004 = 令牌验证失败 + +systemVerificationEmailController10001 = 验证码已发送到你的邮箱 +systemVerificationEmailController10002 = 发送验证码失败,请稍后重试 +systemVerificationEmailController10003 = 验证码验证成功 +systemVerificationEmailController10004 = 验证码无效或已过期 + +SystemCommandOverhaulAppController10001 = 操作成功 + +systemEmailUtil10001 = 发送邮件失败 +systemR10001 = 操作成功 + +#管理端 +# manager.页面,字段 = 用户管理 +VerificationEmailTiTle = 你的验证码 +VerificationEmailContent = 你的验证码是: {0},有效期为 {1} 分钟。 + + +#APP端 + +AppSystemPassWordTip10001 = 两次输入不一致 +AppSystemPassWordTip10002 = 不得包含您的姓名或电子邮件 +AppSystemPassWordTip10003 = 至少包含8个字符 +AppSystemPassWordTip10004 = 包含符号或数字 + +OnboardingOnboarding2ActionSkip = 跳过 +OnboardingOnboarding2TitleOne = 每次都能智能、快速地规划路线。 +OnboardingOnboarding2InfoOne = 优化后的导航将帮助您更快、更安全地到达目的地。 +OnboardingOnboarding2ButtonGetStarted = 开始 +OnboardingOnboarding3ActionSkip = 跳过 +OnboardingOnboarding3TitleOne = 保持合规,驾驶平稳。 +OnboardingOnboarding3InfoOne = 无缝的 EWD 和 CoR 合规性设计,助您保持行稳致远。 +OnboardingOnboarding3ButtonGetStarted = 开始 +OnboardingGetStartedTitleOne = 实现收益最大化,减少麻烦事。 +OnboardingGetStartedInfoOne = 点击“注册”或“登录”以查看您附近正在发生的事情 +OnboardingGetStartedButtonEamil = 继续使用电子邮件 +OnboardingGetStartedButtonGoogle = 继续使用 Google +OnboardingGetStartedButtonApple = 继续使用 AppleID +AuthenticationLoginEmptyStateTitleOne = 登录至 Routez +AuthenticationLoginEmptyStateInfoOne = 欢迎回来!请填写您的详细信息。 +AuthenticationLoginEmptyStatePlaceholderEamil = 邮箱 +AuthenticationLoginEmptyStatePlaceholderPassword = 密码 +AuthenticationLoginEmptyStateInfoTwo = 忘记了密码? +AuthenticationLoginEmptyStateActionReset = 重置 +AuthenticationLoginEmptyStateButtonSignin = 登录 +AuthenticationLoginEmptyStateButtonGoogle = 使用 Google 注册 +AuthenticationLoginEmptyStateButtonApple = 使用 AppleID 注册 +AuthenticationLoginEmptyStateInfoThree = 还没有账户吗? +AuthenticationLoginEmptyStateActionSignup = 注册 +AuthenticationSignUpEmptyStateTitleOne = 注册 +AuthenticationSignUpEmptyStatePlaceholderFullname = 全名 +AuthenticationSignUpEmptyStatePlaceholderEamil = 邮箱 +AuthenticationSignUpEmptyStatePlaceholderPassword = 密码 +AuthenticationSignUpEmptyStatePlaceholderFullnameTips = 请检查全名 +AuthenticationSignUpEmptyStatePlaceholderEamilTips = 请检查邮箱 +AuthenticationSignUpEmptyStatePlaceholderPasswordTips = 请检查密码 +AuthenticationSignUpEmptyStateInfoOne = 通过注册,您即表示同意我们的条款。 +AuthenticationSignUpEmptyStateLegalService = 服务条款 +AuthenticationSignUpEmptyStateInfoTwo = 和 +AuthenticationSignUpEmptyStateLegalpolicy = 隐私协议。 +AuthenticationSignUpEmptyStateButtonSignup = 注册 +AuthenticationSignUpEmptyStateButtonGoogle = 使用 Google 注册 +AuthenticationSignUpEmptyStateButtonApple = 使用 AppleID 注册 +AuthenticationSignUpEmptyStateInfoThree = 已经有账号了吗? +AuthenticationSignUpEmptyStateActionSignin = 登录 +AuthenticationEnterVerificationCodeTitleOne = 即将完成! +AuthenticationEnterVerificationCodeInfoOne = 请查看您的电子邮件收件箱,并输入验证码以验证您的账户。 +AuthenticationEnterVerificationCodeButtonContinue = 继续 +AuthenticationEnterVerificationCodeButtonResend = 重新发送验证码 +AuthenticationForgotPasswordTitleOne = 无法登录? +AuthenticationForgotPasswordInfoOne = 输入你账号绑定的邮箱,Carline 就会给你发个链接,直接重置密码! +AuthenticationForgotPasswordPlaceholderEamil = 邮箱 +AuthenticationForgotPasswordButtonReset = 重置密码 +AuthenticationForgotPasswordButtonReturn = 返回登录页面 +AuthenticationEnterNewPasswordTitleOne = 创建您的新密码 +AuthenticationEnterNewPasswordInfoOne = 您的新密码必须与之前的密码不同。 +AuthenticationEnterNewPasswordPlaceholderPassword = 密码 +AuthenticationEnterNewPasswordPlaceholderRepeat = 密码 +AuthenticationEnterNewPasswordAlertOne = 不得包含您的姓名或电子邮件。 +AuthenticationEnterNewPasswordAlertTwo = 至少 8 个字符 +AuthenticationEnterNewPasswordAlertThree = 包含一个符号或一个数字 +AuthenticationEnterNewPasswordButtonCreate = 创建新密码 +DriverMyProfileTitleOne = 资料 +DriverMyProfileButtonMyProfile = 我的资料 +DriverMyProfileTitleTwo = 通用 +DriverMyProfileActionOne = 职务 +DriverMyProfileActionTwo = 试驾 +DriverMyProfileActionThree = 我的订单 +DriverMyProfileTitleThree = 浏览历史 +DriverMyProfileNavMome = 主页 +DriverMyProfileNavFavourite = 地图 +DriverMyProfileNavMessage = 车队 +DriverMyProfileNavProfile = 资料 +DriverPreTripCheckTitleOne = 行车前检查 +DriverPreTripCheckTitleTwo = 下一站 +DriverPreTripCheckButtonStart = 开始检查 +DriverPreTripCheckButtonOne = 检查完成 +DriverPreTripCheckButtonFinish = 结束检查 +DriverPreTripCheckButtonStartfull = 开始全部检查 +DriverPreTripCheckListTitleOne = 行车前检查 +DriverPreTripCheckListInfoOne = 完成 +DriverPreTripCheckDetailInfoOne = 检查点 +DriverPreTripCheckDetailTitleOne = 所需规格 +DriverPreTripCheckDetailButtonFail = 失败 +DriverPreTripCheckDetailButtonPass = 通过 +DriverPreTripCheckDetailLabelOne = 问题详情 +DriverPreTripCheckDetailTitleTwo = 照片 +DriverPreTripCheckDetailButtonAdd = 添加照片 +DriverPreTripCheckDetailButtonSubmit = 提交 +DriverPreTripCheckHistoryTitleOne = 服务时间 +DriverPreTripCheckHistoryInfoOne = 上次休息 +DriverPreTripCheckHistoryInfoTwo = 下次需要 +DriverPreTripCheckHistoryTitleTwo = 当前路线 +DriverPreTripCheckHistoryTitleThree = 当前车辆 +DriverPreTripCheckHistoryButtonStartInspection = 开始检查 +DriverPreTripCheckHistoryButtonViewDetails = 查看详情 +DriverPreTripCheckHistoryTitleFour = 最近检查 +DriverPreTripCheckHistoryStatusIssues = 发现问题 +DriverPreTripCheckHistoryButtonView = 查看 +DriverJobCenterTitleOne = 卡车 +DriverJobCenterStatusCompleted = 已完成 +DriverJobCenterStatusReamining = 剩余 +DriverJobCenterTitleTwo = 下一个任务 +DriverJobCenterInfoPackages = 个包裹 +DriverJobCenterButtonDeliverTo = 送至主码头 +DriverJobCenterTitleThree = 即将到达的站点 +DriverJobCenterButtonSelect = 选择任务 +DriverCurrentDeliveryTitleOne = 当前配送 +DriverCurrentDeliveryTitleTwo = 包裹 +DriverCurrentDeliveryTitleThree = 配送备注 +DriverCurrentDeliveryButtonStartNav = 开始导航 +DriverCurrentDeliveryCompleteTitleOne = 当前配送 +DriverCurrentDeliveryCompleteTitleTwo = 包裹 +DriverCurrentDeliveryCompleteTitleThree = 配送备注 +DriverCurrentDeliveryCompleteButtonComplete = 完成配送 +TruckTruckParameterTitleOne = 卡车详情 +TruckTruckParameterTitleTwo = 载重能力 +TruckTruckParameterTitleThree = 状态 +TruckTruckParameterButtonAdd = 添加到车队 +TruckVehicleProfileTitleOne = 添加我的车辆 +TruckVehicleProfileInfoOne = 所选车辆将用于路线计算和导航。 +TruckVehicleProfileTitleTwo = 卡车 +TruckVehicleProfileTitleThree = 乘用车 +TruckVehicleProfileButtonAdd = 添加车辆 + +# 新增 +NavigationReferenceNavHomePlaceholderSearch = 去哪里? +NavigationReferenceNavHomeButtonHome = 家 +NavigationReferenceNavHomeButtonDepo = 仓库 +NavigationReferenceNavHomeButtonSaved = 已保存 + +NavigationReferenceNavDockUpPlaceholderSearch = 去哪里? +NavigationReferenceNavDockUpButtonHome = 家 +NavigationReferenceNavDockUpButtonDepo = 仓库 +NavigationReferenceNavDockUpButtonAddPlace = 添加地点 +NavigationReferenceNavDockUpTitleSaved = 已保存的位置 +NavigationReferenceNavDockUpActionSeeAll = 查看全部 +NavigationReferenceNavDockUpButtonAddJobLocation = 添加工作地点 + +NavigationReferenceNavSearchNormalPlaceholderSearch = 去哪里? +NavigationReferenceNavSearchNormalActionCancel = 取消 +NavigationReferenceNavSearchNormalButtonRestaurant = 餐厅 +NavigationReferenceNavSearchNormalButtonCoffee = 咖啡厅 +NavigationReferenceNavSearchNormalButtonATM = ATM取款机 +NavigationReferenceNavSearchNormalButtonShopping = 购物 +NavigationReferenceNavSearchNormalButtonFuel = 加油 +NavigationReferenceNavSearchNormalTitleRecentSearches = 最近 +NavigationReferenceNavSearchNormalActionClear = 清除 + +NavigationReferenceNavSearchSelectPlaceholderYourLocation = 你的位置 +NavigationReferenceNavSearchSelectButtonTruck = 卡车 +NavigationReferenceNavSearchSelectButtonVan = 货车 +NavigationReferenceNavSearchSelectButtonCar = 汽车 +NavigationReferenceNavSearchSelectTitleLeavingNow = 立即出发 +NavigationReferenceNavSearchSelectActionRoutePreferences = 路线偏好 + +NavigationReferenceNavRoutePreferenceTitleRoutePreferences = 路线偏好 +NavigationReferenceNavRoutePreferenceinfoCar = 汽车 +NavigationReferenceNavRoutePreferenceToggleSetMaxVehicleSpeed = 设置最大车速 +NavigationReferenceNavRoutePreferenceInfoOne = 次速度将用于计算旅程时间,并在超速时提醒您 +NavigationReferenceNavRoutePreferenceTitleZoneRegulations = 区域规定 +NavigationReferenceNavRoutePreferenceMenuLicensePlateNumber = 车牌号码 +NavigationReferenceNavRoutePreferenceInfoTwo = 印度尼西亚和菲律宾 +NavigationReferenceNavRoutePreferenceTitleAVOID = 避免 +NavigationReferenceNavRoutePreferenceCheckboxAvoidUTurns = 避免U型转弯 +NavigationReferenceNavRoutePreferenceCheckboxAvoidFerries = 避免杜伦 +NavigationReferenceNavRoutePreferenceCheckboxAvoidHighways = 避免高速公路 +NavigationReferenceNavRoutePreferenceCheckboxAvoidTunnels = 避免隧道 +NavigationReferenceNavRoutePreferenceCheckboxAvoidTollRoads = 避免收费公路 +NavigationReferenceNavRoutePreferenceCheckboxAvoidUnpavedRoads = 避免未铺装道路 + +NavigationReferenceNavigatingTitleArrival = 到达 +NavigationReferenceNavigatingTitleRemaining = 剩余 +NavigationReferenceNavigatingTitleDistance = 距离 + +NavigationReferenceNavOverviewTitleArrival = 到达 +NavigationReferenceNavOverviewTitleAddAStop = 添加站点 +NavigationReferenceNavSearchSelectButtonSearch = 搜索 +NavigationReferenceNavSearchSelectButtonGas = 加油站 +NavigationReferenceNavSearchSelectButtonParking = 停车场 +NavigationReferenceNavSearchSelectButtonRestaurants = 餐厅 +NavigationReferenceNavOverviewTitleQuickSettings = 快速设置 +NavigationReferenceNavOverviewToggleSpeedAlert = 超速报警 +NavigationReferenceNavOverviewToggleOffline = 离线模式 +NavigationReferenceNavOverviewToggleTheme = 主题 +NavigationReferenceNavOverviewButtonMoreSetting = 更多设置 +NavigationReferenceNavOverviewTitleOverview = 概览 + +NavigationReferenceHomePageMenuStartDailyRoute = 开始每日路线 +NavigationReferenceHomePageMenuCurrentRoute = 当前路线 +NavigationReferenceHomePageInfoNoActiveRoute = 无活动路线 +NavigationReferenceHomePageMenuMyDiary = 我的日记 / 日志 +NavigationReferenceHomePageInfoElectronicWorkDiary = 电子工作日记 +NavigationReferenceHomePageMenuTripsReports = 行程和报告 +NavigationReferenceHomePageInfoViewTripHistory = 查看历史行程 +NavigationReferenceHomePageMenuDefectsMaintenance = 缺陷与维护 +NavigationReferenceHomePageInfoVehicleIssues = 车辆问题 + +NavigationReferenceSetVehicleInfoTitleDimensionsWeight = 尺寸和重量 +NavigationReferenceSetVehicleInfoTitleVehicleSpecifications = 车辆规格 +NavigationReferenceSetVehicleInfoinfoOne = 输入包括拖车和负载在内的总尺寸和重量 +NavigationReferenceSetVehicleInfoTitlePhysicalDimensions = 物理尺寸 +NavigationReferenceSetVehicleInfoTitleUnitM = 米 +NavigationReferenceSetVehicleInfoTitleUnitCM = 厘米 +NavigationReferenceSetVehicleInfoTitleHeight = 高度 +NavigationReferenceSetVehicleInfoTitleWidth = 宽度 +NavigationReferenceSetVehicleInfoTitleLength = 长度 +NavigationReferenceSetVehicleInfoTitleWeightSpecifications = 重量规格 +NavigationReferenceSetVehicleInfoTitleTotalWeight = 总重量 +NavigationReferenceSetVehicleInfoTitleUnitTonnes = 吨 +NavigationReferenceSetVehicleInfoInfoTwo = 15吨 = 33,069 磅 +NavigationReferenceSetVehicleInfoButtonNext = 下一项:危险品 + +NavigationReferenceHazardousMaterialsTitleHazardousMaterials = 危险材料 +NavigationReferenceHazardousMaterialsTitleCargoClassification = 货物分类 +NavigationReferenceHazardousMaterialsInfoOne = 选择您将要运输的任何危险材料 +NavigationReferenceHazardousMaterialsTitleHazmatCategories = 危险品类别 +NavigationReferenceHazardousMaterialsToggleExplosives = 爆炸物 +NavigationReferenceHazardousMaterialsToggleGas = 气体 +NavigationReferenceHazardousMaterialsToggleFlammable = 易燃物 +NavigationReferenceHazardousMaterialsTogglePoison = 毒物 +NavigationReferenceHazardousMaterialsToggleRadioactive = 放射性物质 +NavigationReferenceHazardousMaterialsToggleCorrosive = 腐蚀性物质 +NavigationReferenceHazardousMaterialsToggleMiscellaneous = 杂项 +NavigationReferenceHazardousMaterialsButtonNext = 下一项:隧道类别 + +NavigationReferenceTripSummaryTitleTripSummary = 行程摘要 +NavigationReferenceTripSummaryTitleTripCompletedSuccessfully = 行程成功完成 +NavigationReferenceTripSummaryInfoOne = 所有合规要求已满足 +NavigationReferenceTripSummaryTitleTripStatistics = 行程统计 +NavigationReferenceTripSummaryTitleStartTime = 开始时间 +NavigationReferenceTripSummaryTitleEndTime = 结束时间 +NavigationReferenceTripSummaryTitleDistance = 距离 +NavigationReferenceTripSummaryTitleDuration = 持续时间 +NavigationReferenceTripSummaryTitleComplianceStatus = 合规状态 +NavigationReferenceTripSummaryStatusOne = 行程前检查已完成 +NavigationReferenceTripSummaryStatusTwo = 路线配置正确 +NavigationReferenceTripSummaryStatusThree = 休息要求已满足 +NavigationReferenceTripSummaryStatusFour = 未报告任何事件 +NavigationReferenceTripSummaryButtonPDF = PDF +NavigationReferenceTripSummaryButtonDone = 完成 + +TripReportAndProfileDefectsMaintenanceButtonBack = 返回 +TripReportAndProfileDefectsMaintenanceTitleDefectsMaintenance = 缺陷与维护 +TripReportAndProfileDefectsMaintenanceTabOpen = 打开 +TripReportAndProfileDefectsMaintenanceTabResolved = 已解决 +TripReportAndProfileDefectsMaintenanceTitleReported = 已报告 +TripReportAndProfileDefectsMaintenanceTitleLoaction = 位置 +TripReportAndProfileDefectsMaintenanceTitleReporter = 报告人 +TripReportAndProfileDefectsMaintenanceButtonAddNewDefect = 添加新缺陷 + +TripReportAndProfileMyDiaryLogButtonBack = 返回 +TripReportAndProfileMyDiaryLogTitleMyDiaryLog = 我的日记 / 日志 +TripReportAndProfileMyDiaryLogTabToday = 今天 +TripReportAndProfileMyDiaryLogTabWeek = 周 +TripReportAndProfileMyDiaryLogTab28Days = 28天 +TripReportAndProfileMyDiaryLogTitleDailyTotals = 每日总计 +TripReportAndProfileMyDiaryLogTitleDrivingTime = 驾驶时间 +TripReportAndProfileMyDiaryLogTitleOnDutyTime = 值班时间 +TripReportAndProfileMyDiaryLogTitleOffDutyTime = 下班时间 +TripReportAndProfileMyDiaryLogTitleTotalBreaks = 总休息时间 +TripReportAndProfileMyDiaryLogStatusRestDay = 休息日 +TripReportAndProfileMyDiaryLogTitleOffDuty = 下班时间 +TripReportAndProfileMyDiaryLogTitleDriving = 驾驶时间 +TripReportAndProfileMyDiaryLogTitleOnDuty = 在岗时间 +TripReportAndProfileMyDiaryLogStatusCompliant = 合规 +TripReportAndProfileMyDiaryLogStatusViolation = 违规 +TripReportAndProfileMyDiaryLogTitle28DayComplianceOverview = 28天合规概览 +TripReportAndProfileMyDiaryLogStatusOff = 下班 +TripReportAndProfileMyDiaryLogButtonExport = 导出 +TripReportAndProfileMyDiaryLogButtonEmailReport = 电子邮件报告 + +TripReportAndProfileTripsReportsButtonBack = 返回 +TripReportAndProfileTripsReportsTitleTripsReports = 行程 & 报告 +TripReportAndProfileTripsReportsTabRecent = 最近 +TripReportAndProfileTripsReportsTabThisWeek = 本周 +TripReportAndProfileTripsReportsTabThisMonth = 本月 +TripReportAndProfileTripsReportsTitleTotalTrips = 总行程数 +TripReportAndProfileTripsReportsTitleDistance = 距离 +TripReportAndProfileTripsReportsTitleCompliance = 合规性 +TripReportAndProfileTripsReportsTitleDuration = 持续时间 +TripReportAndProfileTripsReportsTitleLoadType = 负载类型 +TripReportAndProfileTripsReportsActionViewDetails = 查看详情 +TripReportAndProfileTripsReportsButtonExportAl = 导出 AI +TripReportAndProfileTripsReportsButtonFilter = 筛选 + +TripReportAndProfileMyProfileDetailButtonBack = 返回 +TripReportAndProfileMyProfileDetailTitleProfile = 个人资料 +TripReportAndProfileMyProfileDetailActionEdit = 编辑 +TripReportAndProfileMyProfileDetailTitleDriverInformation = 驾驶员信息 +TripReportAndProfileMyProfileDetailTitleLicenceNumber = 驾照号码 +TripReportAndProfileMyProfileDetailTitleState = 州 +TripReportAndProfileMyProfileDetailTitleExpiryDate = 过期日期 +TripReportAndProfileMyProfileDetailTitleABN = ABN +TripReportAndProfileMyProfileDetailTitleMyVehicles = 我的车辆 +TripReportAndProfileMyProfileDetailTitleAccountSettings = 账户设置 +TripReportAndProfileMyProfileDetailTitlePushNotifications = 推送通知 +TripReportAndProfileMyProfileDetailTitleLocationServices = 位置服务 +TripReportAndProfileMyProfileDetailTitleAutoBackup = 自动备份 +TripReportAndProfileMyProfileDetailButtonSettings = 设置 +TripReportAndProfileMyProfileDetailButtonSign Out = 退出登录 + +TripReportAndProfileTripInProgressTitleTripInProgress = 行程进行中 +TripReportAndProfileTripInProgressActionActive = 活动 +TripReportAndProfileTripInProgressTitleCurrentLocation = 当前位置 +TripReportAndProfileTripInProgressTitleDestination = 目的地 +TripReportAndProfileTripInProgressTitleTripDuration = 行程时长 +TripReportAndProfileTripInProgressTitleWorkTime = 工作时间 +TripReportAndProfileTripInProgressTitleDriveTime = 驾驶时间 +TripReportAndProfileTripInProgressTitleRestTaken = 休息时间 +TripReportAndProfileTripInProgressTitleQuickActions = 快捷操作 +TripReportAndProfileTripInProgressButtonTakeBreak = 休息 +TripReportAndProfileTripInProgressButtonReportlssue = 报告问题 +TripReportAndProfileTripInProgressButtonFuelStop = 加油 +TripReportAndProfileTripInProgressButtonAddNote = 添加备注 +TripReportAndProfileTripInProgressButtonEndTrip = 结束行程 + +TripReportAndProfileEndTripButtonBack = 返回 +TripReportAndProfileEndTripTitleEndTrip = 结束行程 +TripReportAndProfileEndTripTitleTripSummary = 行程摘要 +TripReportAndProfileEndTripTitleStartLocation = 起点位置 +TripReportAndProfileEndTripTitleEndLocation = 结束位置 +TripReportAndProfileEndTripTitleTotalDistance = 总距离 +TripReportAndProfileEndTripTitleTripDuration = 行程时长 +TripReportAndProfileEndTripTitleFuelUsed = 已用燃油(估计) +TripReportAndProfileEndTripTitleFinalOdometerLu = 最终里程表读数 +TripReportAndProfileEndTripTitleStartOdometer = 开始里程表读数 +TripReportAndProfileEndTripTitleEndOdometer = 结束里程表读数 +TripReportAndProfileEndTripTitleDeliveryConfirmation = 交付确认 +TripReportAndProfileEndTripTitleCargoDeliveredSuccessfully = 货物成功交付 +TripReportAndProfileEndTripTitleDeliveryDocumentationComplete = 交付文件完成 +TripReportAndProfileEndTripTitleVehicleParkedSafely = 车辆安全停放 +TripReportAndProfileEndTripPlaceholderAddTripNotes = 添加行程备注(可选) +TripReportAndProfileEndTripButtonCompleteTrip = 完成行程 + +TripReportAndProfileBreakRestButtonBack = 返回 +TripReportAndProfileBreakRestTitleBreakRest = 休息与恢复 +TripReportAndProfileBreakRestTitleBreakTimer = 休息计时器 +TripReportAndProfileBreakRestInfoOne = 至少 +TripReportAndProfileBreakRestInfoTwo = 所需 +TripReportAndProfileBreakRestTitleWorkTimeSummary = 工作时间总结 +TripReportAndProfileBreakRestTitleTotalWorkTime = 总工作时间 +TripReportAndProfileBreakRestTitleContinuousDriving = 连续驾驶时间 +TripReportAndProfileBreakRestTitleLastBreak = 上次休息时间 +TripReportAndProfileBreakRestTitleTotalBreaksToday = 今天总休息时间 +TripReportAndProfileBreakRestTitleComplianceStatus = 合规状态 +TripReportAndProfileBreakRestTitleDailyWorkLimit = 每日工作限制 +TripReportAndProfileBreakRestTitleWeeklyHours = 每周工时 +TripReportAndProfileBreakRestButtonEndBreakResume = 结束休息并恢复工作 + +TripReportAndProfileTripCompleteTitleTripComplete = 行程完成! +TripReportAndProfileTripCompleteInfoOne = 您的行程已成功记录并保存到您的电子工作日记中。 +TripReportAndProfileTripCompleteTitleTripStatistics = 行程统计 +TripReportAndProfileTripCompleteTitleDistance = 距离 +TripReportAndProfileTripCompleteTitleDuration = 持续时间 +TripReportAndProfileTripCompleteTitleAvgSpeed = 平均速度 +TripReportAndProfileTripCompleteTitleFuelUsed = 燃油消耗 +TripReportAndProfileTripCompleteTitleAllComplianceMet = 所有合规要求已满足 +TripReportAndProfileTripCompleteInfoTwo = 您的行程符合所有NHVR规定 +TripReportAndProfileTripCompleteButtonViewSummary = 查看摘要 +TripReportAndProfileTripCompleteButtonBackToDashboard = 返回仪表板 + +TripReportAndProfileReportIncidentButtonBack = 返回 +TripReportAndProfileReportIncidentTitleReportIncident = 报告事件 +TripReportAndProfileReportIncidentStatusUrgent = 紧急 +TripReportAndProfileReportIncidentTitleIncidentType = 事件类型 +TripReportAndProfileReportIncidentTitleLocationDetails = 位置详情 +TripReportAndProfileReportIncidentTitleCurrentLocation = 当前位置 +TripReportAndProfileReportIncidentPlaceholderOne = 最近的地标或地址 +TripReportAndProfileReportIncidentTitleIncidentDetails = 事件详情 +TripReportAndProfileReportIncidentPlaceholderTwo = 描述发生了什么。 +TripReportAndProfileReportIncidentTitleAnyInjuries = 是否有受伤? +TripReportAndProfileReportIncidentButtonYes = 是 +TripReportAndProfileReportIncidentButtonNo = 否 +TripReportAndProfileReportIncidentTitleEmergencyServicesCalled = 是否呼叫了紧急服务? +TripReportAndProfileReportIncidentTitlePhotosEvidence = 照片和证据 +TripReportAndProfileReportIncidentButtonSubmitReport = 提交报告 +TripReportAndProfileReportIncidentButtonSaveDraft = 保存草稿 + +TripReportAndProfileDailySummaryButtonBack = 返回 +TripReportAndProfileDailySummaryTitleDailySummary = 每日总结 +TripReportAndProfileDailySummaryInfoOne = 工作日完成情况 +TripReportAndProfileDailySummaryTitleTodayPerformance = 今日表现 +TripReportAndProfileDailySummaryTitleTotalDistance = 总距离 +TripReportAndProfileDailySummaryTitleTotalWorkTime = 总工作时间 +TripReportAndProfileDailySummaryTitleTripsCompleted = 完成的行程次数 +TripReportAndProfileDailySummaryTitleTotalBreaks = 总休息时间 +TripReportAndProfileDailySummaryTitleTripDetails = 行程详情 +TripReportAndProfileDailySummaryTitleTrip = 行程 +TripReportAndProfileDailySummaryTitleComplianceSummary = 合规总结 +TripReportAndProfileDailySummaryTitleWorkTimeLimits = 工作时间限制 +TripReportAndProfileDailySummaryTitleRestBreaks = 休息时间 +TripReportAndProfileDailySummaryTitleElectronicWorkDiary = 电子工作日记 +TripReportAndProfileDailySummaryButtonExportReport = 导出报告 +TripReportAndProfileDailySummaryButtonSignOffDay = 签退日 + +AuthenticationAddDriverTitleAddVehicle = 添加车辆 +AuthenticationAddDriverTitleVehicleInformation = 车辆信息 +AuthenticationAddDriverTagRegistration = 车牌号码 +AuthenticationAddDriverTagVIN = 车辆识别号(VIN) +AuthenticationAddDriverTagRegistrationExpiry = 注册到期日志 +AuthenticationAddDriverTagInsuranceExpiry = 保险到期日期 +AuthenticationAddDriverTagVehicleType = 车辆类型 +AuthenticationAddDriverOptionSelectType = 选择类型 +AuthenticationAddDriverTagMake = 品牌 +AuthenticationAddDriverTagModel = 型号 +AuthenticationAddDriverTagYear = 年份 +AuthenticationAddDriverTagColor = 颜色 +AuthenticationAddDriverTagDimensionsCapacity = 尺寸和容量 +AuthenticationAddDriverTagVehicleHeight = 车辆高度 +AuthenticationAddDriverTagVehicleWidth = 车辆宽度 +AuthenticationAddDriverTagVehicleLength = 车辆长度 +AuthenticationAddDriverTagGrossWeight = 总载重 +AuthenticationAddDriverTitleAdditionalSpecifications = 附加规格 +AuthenticationAddDriverTagAxles = 轴数 +AuthenticationAddDriverTagTrailers = 拖车 +AuthenticationAddDriverTagWeightPerAxle = 每轴重量 +AuthenticationAddDriverTagTruckType = 卡车类型 +AuthenticationAddDriverTitleHazardousMaterials = 危险品 +AuthenticationAddDriverTagCarryingHazardousGoods = 运输危险品? +AuthenticationAddDriverButtonNo = 否 +AuthenticationAddDriverButtonYes = 是 +AuthenticationAddDriverButtonAddVehicle = 添加车辆 + +AuthenticationAddVehicleTitleDriverProfile = 驾驶员资料 +AuthenticationAddVehicleTitlePersonalInformation = 个人信息 +AuthenticationAddVehicleTagFirstName = 名 +AuthenticationAddVehicleTagLastName = 姓氏 +AuthenticationAddVehicleTagPhoneNumber = 电话号码 +AuthenticationAddVehicleTitleLicenseCertification = 驾照和认证 +AuthenticationAddVehicleTagDriverLicenseNumber = 驾照号码 +AuthenticationAddVehicleTagLicenseClass = 驾照类别 +AuthenticationAddVehicleTagLicenseState = 驾照状态 +AuthenticationAddVehicleOptionSelect = 选择 +AuthenticationAddVehicleTagLicenseExpiry = 驾照到期日期 +AuthenticationAddVehicleTagMedicalCert = 医疗证明 +AuthenticationAddVehicleTitleHazmatCertification = 危险品认证 +AuthenticationAddVehicleTagHazmatCertified = 是否获得危险品认证? +AuthenticationAddVehicleButtonNo = 否 +AuthenticationAddVehicleButtonYes = 是 +AuthenticationAddVehicleButtonSave = 保存 + +AuthenticationHazmatDialogTitleHazmatCertification = 危险品认证 +AuthenticationHazmatDialogTagHazmatCertified = 是否通过危险品认证? +AuthenticationHazmatDialogButtonNo = 否 +AuthenticationHazmatDialogButtonYes = 是 +AuthenticationHazmatDialogTagHazmatExpiryDate = 危险品有效期 +AuthenticationHazmatDialogOptionSelect = 选择日期 \ No newline at end of file diff --git a/vetti-admin/target/classes/logback.xml b/vetti-admin/target/classes/logback.xml new file mode 100644 index 0000000..ac02724 --- /dev/null +++ b/vetti-admin/target/classes/logback.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vetti-admin/target/classes/mybatis/mybatis-config.xml b/vetti-admin/target/classes/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/vetti-admin/target/classes/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/vetti-common/pom.xml b/vetti-common/pom.xml new file mode 100644 index 0000000..d9c1369 --- /dev/null +++ b/vetti-common/pom.xml @@ -0,0 +1,171 @@ + + + + vetti-service + com.vetti + 3.9.0 + + 4.0.0 + + vetti-common + + + common通用工具 + + + + + + + io.swagger + swagger-models + + + + org.projectlombok + lombok + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + commons-io + commons-io + + + commons-fileupload + commons-fileupload + + + + org.apache.poi + poi-ooxml + + + + + org.yaml + snakeyaml + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + eu.bitwalker + UserAgentUtils + + + + + javax.servlet + javax.servlet-api + + + + org.projectlombok + lombok + + + + + cn.hutool + hutool-all + + + com.squareup.okhttp3 + okhttp + + + io.minio + minio + + + com.sendgrid + sendgrid-java + + + com.google.code.gson + gson + + + + org.apache.httpcomponents + httpmime + + + + + + \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/ai/ElevenLabsClient.java b/vetti-common/src/main/java/com/vetti/common/ai/ElevenLabsClient.java new file mode 100644 index 0000000..6a64d12 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/ai/ElevenLabsClient.java @@ -0,0 +1,199 @@ +package com.vetti.common.ai; + +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import com.google.gson.Gson; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * 文本转换为语音 + */ +public class ElevenLabsClient { + + private static final String BASE_URL = "https://api.elevenlabs.io/v1"; + private final String apiKey; + private final CloseableHttpClient httpClient; + private final Gson gson; + + public ElevenLabsClient(String apiKey) { + this.apiKey = apiKey; + this.httpClient = HttpClients.createDefault(); + this.gson = new Gson(); + } + + /** + * 将文本转换为语音并保存到文件 + * + * @param text 要转换的文本 + * @param voiceId 语音ID (可从ElevenLabs网站获取) + * @param outputFilePath 输出文件路径 + * @throws IOException 网络请求或文件操作异常 + */ + public void textToSpeech(String text, String voiceId, String outputFilePath) throws IOException { + HttpPost httpPost = new HttpPost(BASE_URL + "/text-to-speech/" + voiceId); + httpPost.setHeader("xi-api-key", apiKey); + httpPost.setHeader("Content-Type", "application/json"); + + Map payload = new HashMap<>(); + payload.put("text", text); + payload.put("model_id", "eleven_monolingual_v1"); + payload.put("voice_settings", new VoiceSettings(0.7, 0.5)); + + StringEntity entity = new StringEntity(gson.toJson(payload), ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + HttpEntity responseEntity = response.getEntity(); + if (responseEntity != null) { + try (InputStream inputStream = responseEntity.getContent(); + FileOutputStream outputStream = new FileOutputStream(outputFilePath)) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + } + } + } + + /** + * 获取可用的语音列表 + * + * @return 语音列表响应 + * @throws IOException 网络请求异常 + */ + public VoicesResponse getVoices() throws IOException { + HttpGet httpGet = new HttpGet(BASE_URL + "/voices"); + httpGet.setHeader("xi-api-key", apiKey); + + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + HttpEntity responseEntity = response.getEntity(); + String responseBody = EntityUtils.toString(responseEntity); + return gson.fromJson(responseBody, VoicesResponse.class); + } + } + + // 关闭HTTP客户端 + public void close() throws IOException { + httpClient.close(); + } + + // 语音设置内部类 + private static class VoiceSettings { + private double stability; + private double similarity_boost; + + public VoiceSettings(double stability, double similarity_boost) { + this.stability = stability; + this.similarity_boost = similarity_boost; + } + + // getter方法 + public double getStability() { + return stability; + } + + public double getSimilarity_boost() { + return similarity_boost; + } + } + + // 语音列表响应模型 + public static class VoicesResponse { + private Voice[] voices; + + public Voice[] getVoices() { + return voices; + } + + public void setVoices(Voice[] voices) { + this.voices = voices; + } + + public static class Voice { + private String voice_id; + private String name; + private String category; + private FineTuning fine_tuning; + + public String getVoice_id() { + return voice_id; + } + + public void setVoice_id(String voice_id) { + this.voice_id = voice_id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public FineTuning getFine_tuning() { + return fine_tuning; + } + + public void setFine_tuning(FineTuning fine_tuning) { + this.fine_tuning = fine_tuning; + } + } + } + + // 使用示例 + public static void main(String[] args) { + String apiKey = "sk_5240d8f56cb1eb5225fffcf903f62479884d1af5b3de6812"; + ElevenLabsClient client = new ElevenLabsClient(apiKey); + + try { + // 获取可用语音 + VoicesResponse voicesResponse = client.getVoices(); + if (voicesResponse != null && voicesResponse.getVoices() != null && voicesResponse.getVoices().length > 0) { + System.out.println("Available voices:"); + for (VoicesResponse.Voice voice : voicesResponse.getVoices()) { + System.out.println(voice.getName() + " (ID: " + voice.getVoice_id() + ")"); + } + + // 使用第一个可用语音进行文本转语音 + String firstVoiceId = voicesResponse.getVoices()[0].getVoice_id(); + String text = "Come on, Baby,Come on, Baby,Come on, Baby,Come on, Baby"; + String outputFile = "output.mp3"; + + client.textToSpeech(text, firstVoiceId, outputFile); + System.out.println("Audio saved to: " + outputFile); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + client.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/ai/FineTuning.java b/vetti-common/src/main/java/com/vetti/common/ai/FineTuning.java new file mode 100644 index 0000000..6c845f0 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/ai/FineTuning.java @@ -0,0 +1,23 @@ +package com.vetti.common.ai; + +public class FineTuning { + + private String status; + private String model_id; + + public String getModel_id() { + return model_id; + } + + public void setModel_id(String model_id) { + this.model_id = model_id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/ai/WapiAiClient.java b/vetti-common/src/main/java/com/vetti/common/ai/WapiAiClient.java new file mode 100644 index 0000000..b6da4e3 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/ai/WapiAiClient.java @@ -0,0 +1,159 @@ +package com.vetti.common.ai; + +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import com.google.gson.Gson; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * 发送文本消息到指定的WhatsApp + */ +public class WapiAiClient { + + private static final String BASE_URL = "https://wapi.ai/api"; + private final String apiKey; + private final CloseableHttpClient httpClient; + private final Gson gson; + + public WapiAiClient(String apiKey) { + this.apiKey = apiKey; + this.httpClient = HttpClients.createDefault(); + this.gson = new Gson(); + } + + /** + * 发送文本消息到指定的WhatsApp号码 + * @param phoneNumber 目标电话号码(带国家代码,如:+1234567890) + * @param message 要发送的消息内容 + * @return API响应 + * @throws IOException 网络请求异常 + */ + public WapiResponse sendTextMessage(String phoneNumber, String message) throws IOException { + HttpPost httpPost = new HttpPost(BASE_URL + "/sendMessage"); + httpPost.setHeader("Authorization", "Bearer " + apiKey); + httpPost.setHeader("Content-Type", "application/json"); + + Map payload = new HashMap<>(); + payload.put("phone", phoneNumber); + payload.put("body", message); + + StringEntity entity = new StringEntity(gson.toJson(payload), ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + HttpEntity responseEntity = response.getEntity(); + String responseBody = EntityUtils.toString(responseEntity); + return gson.fromJson(responseBody, WapiResponse.class); + } + } + + /** + * 获取最近的消息 + * @param limit 消息数量限制 + * @return 消息列表 + * @throws IOException 网络请求异常 + */ + public WapiMessagesResponse getRecentMessages(int limit) throws IOException { + HttpGet httpGet = new HttpGet(BASE_URL + "/messages?limit=" + limit); + httpGet.setHeader("Authorization", "Bearer " + apiKey); + + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + HttpEntity responseEntity = response.getEntity(); + String responseBody = EntityUtils.toString(responseEntity); + return gson.fromJson(responseBody, WapiMessagesResponse.class); + } + } + + // 关闭HTTP客户端 + public void close() throws IOException { + httpClient.close(); + } + + // 响应模型类 + public static class WapiResponse { + + private boolean success; + private String message; + private Object data; + + // getter和setter方法 + public boolean isSuccess() { return success; } + public void setSuccess(boolean success) { this.success = success; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Object getData() { return data; } + public void setData(Object data) { this.data = data; } + } + + // 消息响应模型类 + public static class WapiMessagesResponse { + private boolean success; + private Message[] messages; + + // getter和setter方法 + public boolean isSuccess() { return success; } + public void setSuccess(boolean success) { this.success = success; } + public Message[] getMessages() { return messages; } + public void setMessages(Message[] messages) { this.messages = messages; } + + public static class Message { + private String id; + private String phone; + private String body; + private String type; + private String timestamp; + + // getter和setter方法 + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getBody() { return body; } + public void setBody(String body) { this.body = body; } + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getTimestamp() { return timestamp; } + public void setTimestamp(String timestamp) { this.timestamp = timestamp; } + } + } + + // 使用示例 + public static void main(String[] args) { + String apiKey = "your_wapi_ai_api_key"; + WapiAiClient client = new WapiAiClient(apiKey); + + try { + // 发送消息 + WapiResponse response = client.sendTextMessage("+1234567890", "Hello from Wapi.ai Java Client!"); + System.out.println("Message sent: " + response.isSuccess()); + + // 获取最近消息 + WapiMessagesResponse messagesResponse = client.getRecentMessages(5); + if (messagesResponse.isSuccess() && messagesResponse.getMessages() != null) { + System.out.println("Recent messages:"); + for (WapiMessagesResponse.Message msg : messagesResponse.getMessages()) { + System.out.println(msg.getPhone() + ": " + msg.getBody()); + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + client.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} + diff --git a/vetti-common/src/main/java/com/vetti/common/ai/WhisperClient.java b/vetti-common/src/main/java/com/vetti/common/ai/WhisperClient.java new file mode 100644 index 0000000..5d3cf9e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/ai/WhisperClient.java @@ -0,0 +1,113 @@ +package com.vetti.common.ai; + +import cn.hutool.json.JSONObject; +import okhttp3.*; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class WhisperClient { + private static final String API_URL = "https://api.openai.com/v1/audio/transcriptions"; + private static final String MODEL = "whisper-1"; + private final String apiKey; + private final OkHttpClient client; + + // 构造函数,传入API密钥 + public WhisperClient(String apiKey) { + this.apiKey = apiKey; + this.client = new OkHttpClient(); + } + + /** + * 将音频文件转换为文本 + * @param audioFile 音频文件 + * @param options 可选参数 (language, response_format等) + * @return 转换后的文本 + * @throws IOException 网络或文件操作异常 + * @throws WhisperException API返回错误 + */ + public String transcribe(File audioFile, Map options) throws IOException, WhisperException { + // 验证文件是否存在 + if (!audioFile.exists()) { + throw new IllegalArgumentException("音频文件不存在: " + audioFile.getAbsolutePath()); + } + + // 构建请求体 + MultipartBody.Builder bodyBuilder = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("model", MODEL) + .addFormDataPart("file", audioFile.getName(), + RequestBody.create(audioFile, MediaType.parse("audio/*"))); + + // 添加可选参数 + if (options != null) { + for (Map.Entry entry : options.entrySet()) { + bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue()); + } + } + + // 构建请求 + Request request = new Request.Builder() + .url(API_URL) + .header("Authorization", "Bearer " + apiKey) + .post(bodyBuilder.build()) + .build(); + + // 发送请求并处理响应 + try (Response response = client.newCall(request).execute()) { + String responseBody = response.body().string(); + + if (!response.isSuccessful()) { + throw new WhisperException( + "API请求失败: " + response.code() + + ", 详情: " + responseBody + ); + } + + // 解析JSON响应 + JSONObject json = new JSONObject(responseBody); + return ""; + } + } + + /** + * 简化的转写方法,使用默认参数 + */ + public String transcribe(File audioFile) throws IOException, WhisperException { + return transcribe(audioFile, null); + } + + // 自定义异常类,用于处理Whisper API相关错误 + public static class WhisperException extends Exception { + public WhisperException(String message) { + super(message); + } + } + + // 使用示例 + public static void main(String[] args) { + // 替换为你的API密钥 + String apiKey = "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"; + WhisperClient client = new WhisperClient(apiKey); + + // 音频文件路径 + File audioFile = new File("/Users/wangxiangshun/Public/project-aio/vetti/vetti-service/output.mp3"); + + try { + // 添加可选参数,例如指定语言为中文 + Map options = new HashMap<>(); + options.put("language", "zh"); + + String result = client.transcribe(audioFile, options); + System.out.println("转写结果: " + result); + } catch (IOException e) { + System.err.println("IO错误: " + e.getMessage()); + e.printStackTrace(); + } catch (WhisperException e) { + System.err.println("Whisper API错误: " + e.getMessage()); + } catch (IllegalArgumentException e) { + System.err.println("参数错误: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/ai/WhisperTest.java b/vetti-common/src/main/java/com/vetti/common/ai/WhisperTest.java new file mode 100644 index 0000000..a6c811e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/ai/WhisperTest.java @@ -0,0 +1,72 @@ +package com.vetti.common.ai; + +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import cn.hutool.json.JSONUtil; + +public class WhisperTest { + private static final String API_KEY = "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"; + private static final String AUDIO_PATH = "/Users/wangxiangshun/Public/project-aio/vetti/vetti-service/output.mp3"; // 替换为你的音频文件路径 + + public static void main(String[] args) throws Exception { + // 1. 检查音频文件是否存在 + if (!Files.exists(Paths.get(AUDIO_PATH))) { + System.err.println("音频文件不存在: " + AUDIO_PATH); + return; + } + + // 2. 构建multipart/form-data请求(上传文件) + MultipartBodyPublisher multipart = new MultipartBodyPublisher(); + multipart.addPart("model", "whisper-1"); + multipart.addFilePart("file", AUDIO_PATH); + + // 3. 发送请求 + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.openai.com/v1/audio/transcriptions")) + .header("Authorization", "Bearer " + API_KEY) + .POST(multipart.build()) + .build(); + + // 4. 获取并打印完整响应 + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + System.out.println("状态码: " + response.statusCode()); + System.out.println("响应体: " + response.body()); + + // 5. 解析结果(如果成功) + if (response.statusCode() == 200) { + + System.out.println("识别结果: " + JSONUtil.toJsonStr(response.body())); + } + } +} + +// 辅助类:处理multipart/form-data格式 +class MultipartBodyPublisher { + private final Map parts = new HashMap<>(); + private String filePartName; + private String filePath; + + public void addPart(String name, String value) { + parts.put(name, value); + } + + public void addFilePart(String name, String path) { + this.filePartName = name; + this.filePath = path; + } + + public HttpRequest.BodyPublisher build() throws Exception { + // 实际项目中建议使用成熟的HTTP客户端(如OkHttp)处理multipart,此处为简化示例 + // 完整实现需处理边界、文件流等,可参考:https://stackoverflow.com/a/65271748 + return HttpRequest.BodyPublishers.ofString(""); // 仅示例框架,需完善 + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/Anonymous.java b/vetti-common/src/main/java/com/vetti/common/annotation/Anonymous.java new file mode 100644 index 0000000..2d9aba7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +package com.vetti.common.annotation; + +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.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/DataScope.java b/vetti-common/src/main/java/com/vetti/common/annotation/DataScope.java new file mode 100644 index 0000000..3266989 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/DataScope.java @@ -0,0 +1,33 @@ +package com.vetti.common.annotation; + +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.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/DataSource.java b/vetti-common/src/main/java/com/vetti/common/annotation/DataSource.java new file mode 100644 index 0000000..36ec11c --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +package com.vetti.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.vetti.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource +{ + /** + * 切换数据源名称 + */ + public DataSourceType value() default DataSourceType.MASTER; +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/Excel.java b/vetti-common/src/main/java/com/vetti/common/annotation/Excel.java new file mode 100644 index 0000000..d7e540c --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/Excel.java @@ -0,0 +1,197 @@ +package com.vetti.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import com.vetti.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出Excel数据注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel +{ + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽度 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 是否允许内容换行 + */ + public boolean wrapText() default false; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解. + */ + public boolean comboReadDict() default false; + + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串 2图片) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出列头背景颜色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; + + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; + + /** + * 导出单元格背景颜色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type + { + ALL(0), EXPORT(1), IMPORT(2); + private final int value; + + Type(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + public enum ColumnType + { + NUMERIC(0), STRING(1), IMAGE(2), TEXT(3); + private final int value; + + ColumnType(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/Excels.java b/vetti-common/src/main/java/com/vetti/common/annotation/Excels.java new file mode 100644 index 0000000..91078f7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.vetti.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author ruoyi + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + public Excel[] value(); +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/Log.java b/vetti-common/src/main/java/com/vetti/common/annotation/Log.java new file mode 100644 index 0000000..0dd9715 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/Log.java @@ -0,0 +1,51 @@ +package com.vetti.common.annotation; + +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; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + * + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log +{ + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; + + /** + * 排除指定的请求参数 + */ + public String[] excludeParamNames() default {}; +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/RateLimiter.java b/vetti-common/src/main/java/com/vetti/common/annotation/RateLimiter.java new file mode 100644 index 0000000..7901d48 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +package com.vetti.common.annotation; + +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; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.enums.LimitType; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/RepeatSubmit.java b/vetti-common/src/main/java/com/vetti/common/annotation/RepeatSubmit.java new file mode 100644 index 0000000..af51f00 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.vetti.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @author ruoyi + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/vetti-common/src/main/java/com/vetti/common/annotation/Sensitive.java b/vetti-common/src/main/java/com/vetti/common/annotation/Sensitive.java new file mode 100644 index 0000000..f6be176 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/annotation/Sensitive.java @@ -0,0 +1,24 @@ +package com.vetti.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.vetti.common.config.serializer.SensitiveJsonSerializer; +import com.vetti.common.enums.DesensitizedType; + +/** + * 数据脱敏注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveJsonSerializer.class) +public @interface Sensitive +{ + DesensitizedType desensitizedType(); +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/HereMapsProperties.java b/vetti-common/src/main/java/com/vetti/common/config/HereMapsProperties.java new file mode 100644 index 0000000..96e7239 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/HereMapsProperties.java @@ -0,0 +1,21 @@ +package com.vetti.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author ID + * @date 2025/9/1 15:54 + */ +@Component +@ConfigurationProperties(prefix = "here-map") // 配置前缀 +@Data +public class HereMapsProperties { + + private String apiKey; + private String geocodingApiUrl; + private String reverseGeocodingApiUrl; + private String routerApiUrl; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/HttpClientConfig.java b/vetti-common/src/main/java/com/vetti/common/config/HttpClientConfig.java new file mode 100644 index 0000000..d8a5c59 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/HttpClientConfig.java @@ -0,0 +1,41 @@ +package com.vetti.common.config; + +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.net.http.HttpClient; +import java.time.Duration; +import java.util.concurrent.Executors; + +/** + * @author ID + * @date 2025/9/1 16:21 + */ +@Configuration +public class HttpClientConfig { + + @Value("${http.client.connect-timeout-seconds:10}") + private Integer connectTimeoutSeconds; + + + /** + * 创建并配置 HttpClient Bean. + * 该实例线程安全,建议在整个应用中复用。 + * + * @return 配置好的 HttpClient 实例 + */ + @Bean + public HttpClient httpClient() { + return HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) // 优先使用 HTTP/2,若服务器不支持则降级至 HTTP/1.1 + .connectTimeout(Duration.ofSeconds(connectTimeoutSeconds)) // 建立连接的超时时间 + .followRedirects(HttpClient.Redirect.NORMAL) // 处理重定向策略 (NORMAL: 同协议重定向) + .executor(Executors.newVirtualThreadPerTaskExecutor()) // 使用虚拟线程执行器(Java 21+,可选) + .build(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/InternationalProperties.java b/vetti-common/src/main/java/com/vetti/common/config/InternationalProperties.java new file mode 100644 index 0000000..5cca1df --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/InternationalProperties.java @@ -0,0 +1,84 @@ +package com.vetti.common.config; + +import cn.hutool.core.util.StrUtil; +import com.vetti.common.enums.InternationalLangTypeEnum; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * 国际化配置属性 + * + * @author wangxiangshun + */ +@Data +@Component +@ConfigurationProperties(prefix = "vetti.international") +public class InternationalProperties { + + /** + * 是否启用,默认为False + */ + private boolean enable = true; + + /** + * 请求语言参数位置(HEADER/PARAM),默认为HEADER + */ + private RequestLangParamPosition paramPosition = RequestLangParamPosition.HEADER; + + /** + * 请求语言参数名称,默认为lang + */ + private String paramName = "lang"; + + /** + * 获取请求语言 + * + * @return 请求语言 + */ + public String getLang() { + HttpServletRequest request = null; + try { + request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } catch (Exception ex) { + // ignore + } + + if (null == request) { + return null; + } + + String lang; + if (paramPosition == RequestLangParamPosition.HEADER) { + lang = request.getHeader(paramName); + } else { + lang = request.getParameter(paramName); + } + //语言类型特殊转换 + if(StrUtil.isNotEmpty(lang)){ + lang = lang.replaceAll("-Hans-","_").replaceAll("-Hant-","_"). + replaceAll("_Hans_","_").replaceAll("_Hant_","_").replaceAll("-","_"); + if(lang.contains("en") || lang.contains("EN")){ + lang = InternationalLangTypeEnum.INTERNATIONAL_LANG_TYPE_ENUM_EN.getCode(); + }else if(lang.contains("zh") || lang.contains("ZH")){ + lang = InternationalLangTypeEnum.INTERNATIONAL_LANG_TYPE_ENUM_ZH.getCode(); + } + }else { + lang = InternationalLangTypeEnum.INTERNATIONAL_LANG_TYPE_ENUM_EN.getCode(); + } + + return lang; + } + + /** + * 请求语言参数位置 + */ + public enum RequestLangParamPosition { + + HEADER, PARAM + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/MinioConfig.java b/vetti-common/src/main/java/com/vetti/common/config/MinioConfig.java new file mode 100644 index 0000000..9300376 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/MinioConfig.java @@ -0,0 +1,46 @@ +package com.vetti.common.config; + + +import io.minio.MinioClient; +import lombok.Data; +import okhttp3.OkHttpClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.TimeUnit; + +@Data +@Configuration +public class MinioConfig { + + @Value("${fs.minio.endpoint}") + private String endpoint; + + @Value("${fs.minio.access-key}") + private String accessKey; + + @Value("${fs.minio.secret-key}") + private String secretKey; + + + // 1. 配置 OkHttp 客户端(设置超时参数) + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .connectTimeout(300, TimeUnit.SECONDS) // 连接超时 + .readTimeout(600, TimeUnit.SECONDS) // 读取超时 + .writeTimeout(600, TimeUnit.SECONDS) // 写入超时 + .retryOnConnectionFailure(true) // 连接失败重试 + .build(); + + /** + * 创建 MinIO 客户端实例 + */ + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint(endpoint) + .credentials(accessKey, secretKey) + .httpClient(okHttpClient) + .build(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/RuoYiConfig.java b/vetti-common/src/main/java/com/vetti/common/config/RuoYiConfig.java new file mode 100644 index 0000000..b8e8413 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/RuoYiConfig.java @@ -0,0 +1,122 @@ +package com.vetti.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "vetti") +public class RuoYiConfig +{ + /** 项目名称 */ + private String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 上传路径 */ + private static String profile; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + /** 验证码类型 */ + private static String captchaType; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + this.copyrightYear = copyrightYear; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + RuoYiConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + RuoYiConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RuoYiConfig.captchaType = captchaType; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() + { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() + { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() + { + return getProfile() + "/upload"; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/TwilioConfig.java b/vetti-common/src/main/java/com/vetti/common/config/TwilioConfig.java new file mode 100644 index 0000000..3e137a7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/TwilioConfig.java @@ -0,0 +1,27 @@ +package com.vetti.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author ID + * @date 2025/8/28 14:00 + */ +@Data +@Component +@ConfigurationProperties(prefix = "twilio.sendgrid") +public class TwilioConfig { + private String apiKey; + private String fromEmail; + private String fromName; + private TemplateIds templateIds; + + + @Data + public static class TemplateIds { + private String routezVerificationCode; + + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/config/VerificationEmailConfig.java b/vetti-common/src/main/java/com/vetti/common/config/VerificationEmailConfig.java new file mode 100644 index 0000000..9385865 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/VerificationEmailConfig.java @@ -0,0 +1,17 @@ +package com.vetti.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author ID + * @date 2025/8/28 14:01 + */ +@Data +@Component +@ConfigurationProperties(prefix = "verification.code.email") +public class VerificationEmailConfig { + private int length; + private int expirationMinutes; +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/config/serializer/SensitiveJsonSerializer.java b/vetti-common/src/main/java/com/vetti/common/config/serializer/SensitiveJsonSerializer.java new file mode 100644 index 0000000..a47b7da --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/config/serializer/SensitiveJsonSerializer.java @@ -0,0 +1,67 @@ +package com.vetti.common.config.serializer; + +import java.io.IOException; +import java.util.Objects; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.vetti.common.annotation.Sensitive; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.enums.DesensitizedType; +import com.vetti.common.utils.SecurityUtils; + +/** + * 数据脱敏序列化过滤 + * + * @author ruoyi + */ +public class SensitiveJsonSerializer extends JsonSerializer implements ContextualSerializer +{ + private DesensitizedType desensitizedType; + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException + { + if (desensitization()) + { + gen.writeString(desensitizedType.desensitizer().apply(value)); + } + else + { + gen.writeString(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException + { + Sensitive annotation = property.getAnnotation(Sensitive.class); + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) + { + this.desensitizedType = annotation.desensitizedType(); + return this; + } + return prov.findValueSerializer(property.getType(), property); + } + + /** + * 是否需要脱敏处理 + */ + private boolean desensitization() + { + try + { + LoginUser securityUser = SecurityUtils.getLoginUser(); + // 管理员不脱敏 + return !securityUser.getUser().isAdmin(); + } + catch (Exception e) + { + return true; + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/CacheConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/CacheConstants.java new file mode 100644 index 0000000..4a78ab7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/CacheConstants.java @@ -0,0 +1,61 @@ +package com.vetti.common.constant; + +/** + * 缓存的key 常量 + * + * @author ruoyi + */ +public class CacheConstants +{ + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; + + /** + * 缓存有效期,默认720(分钟) + */ + public final static long EXPIRATION = 7200; + + /** + * 缓存刷新时间,默认120(分钟) + */ + public final static long REFRESH_TIME = 120; + + + /** + * 邮箱验证码 key + */ + public static final String VERIFICATION_EMAIL_CODE_KEY = "verification:email:code:"; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/Constants.java b/vetti-common/src/main/java/com/vetti/common/constant/Constants.java new file mode 100644 index 0000000..1b4d966 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/Constants.java @@ -0,0 +1,190 @@ +package com.vetti.common.constant; + +import java.util.Locale; +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 系统语言 + */ + public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 所有权限标识 + */ + public static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + public static final String SUPER_ADMIN = "admin"; + + /** + * 角色权限分隔符 + */ + public static final String ROLE_DELIMETER = ","; + + /** + * 权限标识分隔符 + */ + public static final String PERMISSION_DELIMETER = ","; + + /** + * 验证码有效期(分钟) + */ + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "com.vetti" }; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.vetti.quartz.task" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.vetti.common.utils.file", "com.vetti.common.config", "com.vetti.generator" }; + + /** + * 系统共通常量-0代表未删除 + */ + public static final String DEL_FLAG_ZERO = "0"; + + /** + * 系统共通常量-2代表删除 + */ + public static final String DEL_FLAG_ONE = "2"; + + /** + * 系统业务-国际化语言类型 + */ + public static final String SYSTEM_LANGUAGE_TYPE = "zh_CN,en_US"; + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/GenConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/GenConstants.java new file mode 100644 index 0000000..877c472 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/GenConstants.java @@ -0,0 +1,117 @@ +package com.vetti.common.constant; + +/** + * 代码生成通用常量 + * + * @author ruoyi + */ +public class GenConstants +{ + /** 单表(增删改查) */ + public static final String TPL_CRUD = "crud"; + + /** 树表(增删改查) */ + public static final String TPL_TREE = "tree"; + + /** 主子表(增删改查) */ + public static final String TPL_SUB = "sub"; + + /** 树编码字段 */ + public static final String TREE_CODE = "treeCode"; + + /** 树父编码字段 */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** 树名称字段 */ + public static final String TREE_NAME = "treeName"; + + /** 上级菜单ID字段 */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** 上级菜单名称字段 */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** 数据库字符串类型 */ + public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; + + /** 数据库文本类型 */ + public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; + + /** 数据库时间类型 */ + public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; + + /** 数据库数字类型 */ + public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal" }; + + /** 页面不需要编辑字段 */ + public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; + + /** 页面不需要显示的列表字段 */ + public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time" }; + + /** 页面不需要查询字段 */ + public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark" }; + + /** Entity基类字段 */ + public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; + + /** Tree基类字段 */ + public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; + + /** 文本框 */ + public static final String HTML_INPUT = "input"; + + /** 文本域 */ + public static final String HTML_TEXTAREA = "textarea"; + + /** 下拉框 */ + public static final String HTML_SELECT = "select"; + + /** 单选框 */ + public static final String HTML_RADIO = "radio"; + + /** 复选框 */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** 日期控件 */ + public static final String HTML_DATETIME = "datetime"; + + /** 图片上传控件 */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** 富文本控件 */ + public static final String HTML_EDITOR = "editor"; + + /** 字符串类型 */ + public static final String TYPE_STRING = "String"; + + /** 整型 */ + public static final String TYPE_INTEGER = "Integer"; + + /** 长整型 */ + public static final String TYPE_LONG = "Long"; + + /** 浮点型 */ + public static final String TYPE_DOUBLE = "Double"; + + /** 高精度计算类型 */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** 时间类型 */ + public static final String TYPE_DATE = "Date"; + + /** 模糊查询 */ + public static final String QUERY_LIKE = "LIKE"; + + /** 相等查询 */ + public static final String QUERY_EQ = "EQ"; + + /** 需要 */ + public static final String REQUIRE = "1"; +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/HttpStatus.java b/vetti-common/src/main/java/com/vetti/common/constant/HttpStatus.java new file mode 100644 index 0000000..8e65398 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/HttpStatus.java @@ -0,0 +1,94 @@ +package com.vetti.common.constant; + +/** + * 返回状态码 + * + * @author ruoyi + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/LoginConstant.java b/vetti-common/src/main/java/com/vetti/common/constant/LoginConstant.java new file mode 100644 index 0000000..186861c --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/LoginConstant.java @@ -0,0 +1,64 @@ +package com.vetti.common.constant; + +/** + * ClassName: LoginConstant + * Description: + * Datetime: 2025/08/30 14:05 + * Author: wangxiangshun + */ +public class LoginConstant { + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + public final static String LOGIN_IN_LOCK = "login:lock:"; + + public final static String LOGIN_IN_CODE = "login:code:"; + + public final static Integer LOGIN_IN_ATTEMPTS = 10; + + public final static Integer SEND_CODE_ATTEMPTS = 15; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "hamkke_codes:"; + + + /** + * 验证码 redis key + */ + public static final String ONEKEY_KEY = "hamkke_onekey:"; + + /** + * 取手机验证码的key值的前缀 + */ + public static final String VER_CODE_PREFIX = "ver_:"; + + /** + * 短信 验证码有效期(分钟) + */ + public static final Long SMS_CAPTCHA_EXPIRATION = 5L; + + public final static String IOS = "IOS"; + + public final static String ANDROID = "Android"; + + public final static String APP = "App"; + + public final static String MINIAPP = "miniApp"; + + public static final String WX = "wx"; + + public static final String CODE = "code"; + + public static final String PHONE = "phone"; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY_MINIAPP = "login_user:mini:"; + + public static final String LOGIN_USER_KEY_APP = "login_user:app:"; +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/ScheduleConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/ScheduleConstants.java new file mode 100644 index 0000000..70be2df --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.vetti.common.constant; + +/** + * 任务调度通用常量 + * + * @author ruoyi + */ +public class ScheduleConstants +{ + public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; + + /** 执行目标key */ + public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; + + /** 默认 */ + public static final String MISFIRE_DEFAULT = "0"; + + /** 立即触发执行 */ + public static final String MISFIRE_IGNORE_MISFIRES = "1"; + + /** 触发一次执行 */ + public static final String MISFIRE_FIRE_AND_PROCEED = "2"; + + /** 不触发立即执行 */ + public static final String MISFIRE_DO_NOTHING = "3"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/SecurityConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/SecurityConstants.java new file mode 100644 index 0000000..96c4ceb --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/SecurityConstants.java @@ -0,0 +1,49 @@ +package com.vetti.common.constant; + +/** + * 权限相关通用常量 + * + * @author hamkke + */ +public class SecurityConstants +{ + /** + * 用户ID字段 + */ + public static final String DETAILS_USER_ID = "user_id"; + + /** + * 用户名字段 + */ + public static final String DETAILS_USERNAME = "username"; + + /** + * 授权信息字段 + */ + public static final String AUTHORIZATION_HEADER = "Authorization"; + + /** + * 请求来源 + */ + public static final String FROM_SOURCE = "from-source"; + + /** + * 内部请求 + */ + public static final String INNER = "inner"; + + /** + * 用户标识 + */ + public static final String USER_KEY = "user_key"; + + /** + * 登录用户 + */ + public static final String LOGIN_USER = "login_user"; + + /** + * 角色权限 + */ + public static final String ROLE_PERMISSION = "role_permission"; +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/SysDictDataConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/SysDictDataConstants.java new file mode 100644 index 0000000..ce6b2df --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/SysDictDataConstants.java @@ -0,0 +1,18 @@ +package com.vetti.common.constant; + +/** + * SysDictData 常量 + */ +public class SysDictDataConstants { + + /** + * 任务分配情况-已分配 + */ + public static final String COMMAND_TASK_TASK_ALLOCATE_KEY = "assigned";// + + /** + * 任务分配情况-未分配 + */ + public static final String COMMAND_TASK_TASK_UNALLOCATE_KEY = "unassigned"; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/TokenConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/TokenConstants.java new file mode 100644 index 0000000..b7c557d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/TokenConstants.java @@ -0,0 +1,20 @@ +package com.vetti.common.constant; + +/** + * Token的Key常量 + * + * @author wangxiangshun + */ +public class TokenConstants +{ + /** + * 令牌前缀 + */ + public static final String PREFIX = "Bearer "; + + /** + * 令牌秘钥 + */ + public final static String SECRET = "abcdefghijklmnopqrstuvwxyz"; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/constant/UserConstants.java b/vetti-common/src/main/java/com/vetti/common/constant/UserConstants.java new file mode 100644 index 0000000..91051ec --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/constant/UserConstants.java @@ -0,0 +1,81 @@ +package com.vetti.common.constant; + +/** + * 用户常量信息 + * + * @author ruoyi + */ +public class UserConstants +{ + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色正常状态 */ + public static final String ROLE_NORMAL = "0"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验是否唯一的返回标识 */ + public final static boolean UNIQUE = true; + public final static boolean NOT_UNIQUE = false; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + public static final int PASSWORD_MAX_LENGTH = 20; +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/controller/BaseController.java b/vetti-common/src/main/java/com/vetti/common/core/controller/BaseController.java new file mode 100644 index 0000000..0842575 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/controller/BaseController.java @@ -0,0 +1,221 @@ +package com.vetti.common.core.controller; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; + +import com.vetti.common.core.page.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.vetti.common.constant.HttpStatus; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.PageUtils; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author ruoyi + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) + { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() + { + @Override + public void setAsText(String text) + { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() + { + PageUtils.startPage(); + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + PageHelper.orderBy(orderBy); + } + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() + { + PageUtils.clearPage(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TableDataInfo getDataTable(List list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(new PageInfo(list).getTotal()); + return rspData; + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(Object data) + { + return AjaxResult.success(data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } + + /** + * 返回警告消息 + */ + public AjaxResult warn(String message) + { + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + */ + public LoginUser getLoginUser() + { + return SecurityUtils.getLoginUser(); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() + { + return getLoginUser().getUsername(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TablePageDataInfo getPageDataTable(List list) + { + TablePageDataInfo pageDataInfo = new TablePageDataInfo(); + + TableAppDataInfo rspData = new TableAppDataInfo(); + rspData.setData(list); + rspData.setTotal(new PageInfo(list).getTotal()); + rspData.setPages(new PageInfo(list).getPages()); + rspData.setPageNum(new PageInfo(list).getPageNum()); + rspData.setHasNextPage(new PageInfo(list).isHasNextPage()); + pageDataInfo.setData(rspData); + pageDataInfo.setCode(HttpStatus.SUCCESS); + + return pageDataInfo; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/AjaxResult.java b/vetti-common/src/main/java/com/vetti/common/core/domain/AjaxResult.java new file mode 100644 index 0000000..36b73f2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/AjaxResult.java @@ -0,0 +1,216 @@ +package com.vetti.common.core.domain; + +import java.util.HashMap; +import java.util.Objects; +import com.vetti.common.constant.HttpStatus; +import com.vetti.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author ruoyi + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult warn(String msg) + { + return AjaxResult.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult warn(String msg, Object data) + { + return new AjaxResult(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } + + /** + * 是否为成功消息 + * + * @return 结果 + */ + public boolean isSuccess() + { + return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG)); + } + + /** + * 是否为警告消息 + * + * @return 结果 + */ + public boolean isWarn() + { + return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG)); + } + + /** + * 是否为错误消息 + * + * @return 结果 + */ + public boolean isError() + { + return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG)); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/BaseEntity.java b/vetti-common/src/main/java/com/vetti/common/core/domain/BaseEntity.java new file mode 100644 index 0000000..23e5ac7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/BaseEntity.java @@ -0,0 +1,159 @@ +package com.vetti.common.core.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.vetti.common.utils.SecurityUtils; + +/** + * Entity基类 + * + * @author ruoyi + */ +public class BaseEntity implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 搜索值 + */ + @JsonIgnore + private String searchValue; + + /** + * 创建者 + */ + private String createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private String updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * 备注 + */ + private String remark; + + /** + * 删除标志(0代表存在 2代表删除) + */ + private String delFlag; + + /** + * 版本号 + */ + private Long versionNo; + + /** + * 请求参数 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getSearchValue() { + return searchValue; + } + + public void setSearchValue(String searchValue) { + this.searchValue = searchValue; + } + + public String getCreateBy() { + return createBy; + } + + public void setCreateBy(String createBy) { + this.createBy = createBy; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public String getUpdateBy() { + return updateBy; + } + + public void setUpdateBy(String updateBy) { + this.updateBy = updateBy; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getDelFlag() { + return delFlag; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public Long getVersionNo() { + return versionNo; + } + + public void setVersionNo(Long versionNo) { + this.versionNo = versionNo; + } + + public Map getParams() { + if (params == null) { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + public void setCreate() { + this.setCreateBy(SecurityUtils.getLoginUser().getUsername()); + this.setUpdateBy(SecurityUtils.getLoginUser().getUsername()); + this.setCreateTime(new Date()); + this.setUpdateTime(new Date()); + this.setDelFlag("0"); + } + + public void setUpdate() { + this.setUpdateBy("system"); + this.setUpdateTime(new Date()); + } + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/MinioFileInfo.java b/vetti-common/src/main/java/com/vetti/common/core/domain/MinioFileInfo.java new file mode 100644 index 0000000..402d51b --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/MinioFileInfo.java @@ -0,0 +1,46 @@ +package com.vetti.common.core.domain; + +import lombok.Data; + +import java.time.ZonedDateTime; +import java.util.Map; + +/** + * 文件信息DTO类,用于封装文件的详细信息 + * + * @author ID + * @date 2025/8/26 22:57 + */ +@Data +public class MinioFileInfo { + /** + * 文件名称(包含路径) + */ + private String fileName; + + /** + * 文件大小(单位:字节) + */ + private long size; + + /** + * 最后修改时间 + */ + private ZonedDateTime lastModified; + + /** + * 文件内容类型(MIME类型) + */ + private String contentType; + + /** + * 是否为目录 + */ + private boolean isDirectory; + + /** + * 文件元数据信息 + */ + private Map metadata; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/R.java b/vetti-common/src/main/java/com/vetti/common/core/domain/R.java new file mode 100644 index 0000000..75cdbd2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/R.java @@ -0,0 +1,116 @@ +package com.vetti.common.core.domain; + +import java.io.Serializable; +import com.vetti.common.constant.HttpStatus; +import com.vetti.common.utils.MessageUtils; + +/** + * 响应信息主体 + * + * @author ruoyi + */ +public class R implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = HttpStatus.SUCCESS; + + /** 失败 */ + public static final int FAIL = HttpStatus.ERROR; + + private int code; + + private String msg; + + private T data; + + public static R ok() + { + return restResult(null, SUCCESS, MessageUtils.messageCustomize("systemR10001")); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, MessageUtils.messageCustomize("systemR10001")); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, MessageUtils.messageCustomize("systemR10001")); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, MessageUtils.messageCustomize("systemR10001")); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public T getData() + { + return data; + } + + public void setData(T data) + { + this.data = data; + } + + public static Boolean isError(R ret) + { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/TreeEntity.java b/vetti-common/src/main/java/com/vetti/common/core/domain/TreeEntity.java new file mode 100644 index 0000000..997dac0 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.vetti.common.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree基类 + * + * @author ruoyi + */ +public class TreeEntity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 祖级列表 */ + private String ancestors; + + /** 子部门 */ + private List children = new ArrayList<>(); + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/TreeSelect.java b/vetti-common/src/main/java/com/vetti/common/core/domain/TreeSelect.java new file mode 100644 index 0000000..2a1b357 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/TreeSelect.java @@ -0,0 +1,93 @@ +package com.vetti.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysDept; +import com.vetti.common.core.domain.entity.SysMenu; +import com.vetti.common.utils.StringUtils; + +/** + * Treeselect树结构实体类 + * + * @author ruoyi + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 节点禁用 */ + private boolean disabled = false; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.disabled = StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public boolean isDisabled() + { + return disabled; + } + + public void setDisabled(boolean disabled) + { + this.disabled = disabled; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/dto/SystemUserDto.java b/vetti-common/src/main/java/com/vetti/common/core/domain/dto/SystemUserDto.java new file mode 100644 index 0000000..c6cabb9 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/dto/SystemUserDto.java @@ -0,0 +1,6 @@ +package com.vetti.common.core.domain.dto; + +public class SystemUserDto { + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDept.java b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDept.java new file mode 100644 index 0000000..a3f2fbc --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDept.java @@ -0,0 +1,203 @@ +package com.vetti.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 部门表 sys_dept + * + * @author ruoyi + */ +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + private Long deptId; + + /** 父部门ID */ + private Long parentId; + + /** 祖级列表 */ + private String ancestors; + + /** 部门名称 */ + private String deptName; + + /** 显示顺序 */ + private Integer orderNum; + + /** 负责人 */ + private String leader; + + /** 联系电话 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 部门状态:0正常,1停用 */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 父部门名称 */ + private String parentName; + + /** 子部门 */ + private List children = new ArrayList(); + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getLeader() + { + return leader; + } + + public void setLeader(String leader) + { + this.leader = leader; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + public void setPhone(String phone) + { + this.phone = phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deptId", getDeptId()) + .append("parentId", getParentId()) + .append("ancestors", getAncestors()) + .append("deptName", getDeptName()) + .append("orderNum", getOrderNum()) + .append("leader", getLeader()) + .append("phone", getPhone()) + .append("email", getEmail()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictData.java b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..114bc19 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictData.java @@ -0,0 +1,179 @@ +package com.vetti.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import com.vetti.common.international.International; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author ruoyi + */ +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @Excel(name = "字典标签") + @International(type = "sys_dict_label") + private String dictLabel; + + /** 字典键值 */ + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + private String cssClass; + + /** 表格字典样式 */ + private String listClass; + + /** 是否默认(Y是 N否) */ + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictCode() + { + return dictCode; + } + + public void setDictCode(Long dictCode) + { + this.dictCode = dictCode; + } + + public Long getDictSort() + { + return dictSort; + } + + public void setDictSort(Long dictSort) + { + this.dictSort = dictSort; + } + + @NotBlank(message = "字典标签不能为空") + @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") + public String getDictLabel() + { + return dictLabel; + } + + public void setDictLabel(String dictLabel) + { + this.dictLabel = dictLabel; + } + + @NotBlank(message = "字典键值不能为空") + @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") + public String getDictValue() + { + return dictValue; + } + + public void setDictValue(String dictValue) + { + this.dictValue = dictValue; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") + public String getCssClass() + { + return cssClass; + } + + public void setCssClass(String cssClass) + { + this.cssClass = cssClass; + } + + public String getListClass() + { + return listClass; + } + + public void setListClass(String listClass) + { + this.listClass = listClass; + } + + public boolean getDefault() + { + return UserConstants.YES.equals(this.isDefault); + } + + public String getIsDefault() + { + return isDefault; + } + + public void setIsDefault(String isDefault) + { + this.isDefault = isDefault; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictCode", getDictCode()) + .append("dictSort", getDictSort()) + .append("dictLabel", getDictLabel()) + .append("dictValue", getDictValue()) + .append("dictType", getDictType()) + .append("cssClass", getCssClass()) + .append("listClass", getListClass()) + .append("isDefault", getIsDefault()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictType.java b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..e4868bd --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysDictType.java @@ -0,0 +1,96 @@ +package com.vetti.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 字典类型表 sys_dict_type + * + * @author ruoyi + */ +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictId() + { + return dictId; + } + + public void setDictId(Long dictId) + { + this.dictId = dictId; + } + + @NotBlank(message = "字典名称不能为空") + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") + public String getDictName() + { + return dictName; + } + + public void setDictName(String dictName) + { + this.dictName = dictName; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictId", getDictId()) + .append("dictName", getDictName()) + .append("dictType", getDictType()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysMenu.java b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..ed31a25 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysMenu.java @@ -0,0 +1,278 @@ +package com.vetti.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import com.vetti.common.international.International; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 菜单权限表 sys_menu + * + * @author ruoyi + */ +public class SysMenu extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + private Long menuId; + + /** 菜单名称 */ + @International(type = "sys_menu_name") + private String menuName; + + /** 父菜单名称 */ + @International(type = "sys_menu_parent_name") + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 路由地址 */ + private String path; + + /** 组件路径 */ + private String component; + + /** 路由参数 */ + private String query; + + /** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */ + private String routeName; + + /** 是否为外链(0是 1否) */ + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + private String visible; + + /** 菜单状态(0正常 1停用) */ + private String status; + + /** 权限字符串 */ + private String perms; + + /** 菜单图标 */ + private String icon; + + /** 子菜单 */ + private List children = new ArrayList(); + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() + { + return menuName; + } + + public void setMenuName(String menuName) + { + this.menuName = menuName; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public String getRouteName() + { + return routeName; + } + + public void setRouteName(String routeName) + { + this.routeName = routeName; + } + + public String getIsFrame() + { + return isFrame; + } + + public void setIsFrame(String isFrame) + { + this.isFrame = isFrame; + } + + public String getIsCache() + { + return isCache; + } + + public void setIsCache(String isCache) + { + this.isCache = isCache; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() + { + return menuType; + } + + public void setMenuType(String menuType) + { + this.menuType = menuType; + } + + public String getVisible() + { + return visible; + } + + public void setVisible(String visible) + { + this.visible = visible; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() + { + return perms; + } + + public void setPerms(String perms) + { + this.perms = perms; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("path", getPath()) + .append("component", getComponent()) + .append("query", getQuery()) + .append("routeName", getRouteName()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysRole.java b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysRole.java new file mode 100644 index 0000000..be7c560 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysRole.java @@ -0,0 +1,241 @@ +package com.vetti.common.core.domain.entity; + +import java.util.Set; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 角色表 sys_role + * + * @author ruoyi + */ +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Excel(name = "角色排序") + private Integer roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + private Long[] deptIds; + + /** 角色菜单权限 */ + private Set permissions; + + public SysRole() + { + + } + + public SysRole(Long roleId) + { + this.roleId = roleId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getRoleSort() + { + return roleSort; + } + + public void setRoleSort(Integer roleSort) + { + this.roleSort = roleSort; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + public Long[] getMenuIds() + { + return menuIds; + } + + public void setMenuIds(Long[] menuIds) + { + this.menuIds = menuIds; + } + + public Long[] getDeptIds() + { + return deptIds; + } + + public void setDeptIds(Long[] deptIds) + { + this.deptIds = deptIds; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("roleName", getRoleName()) + .append("roleKey", getRoleKey()) + .append("roleSort", getRoleSort()) + .append("dataScope", getDataScope()) + .append("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysUser.java b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysUser.java new file mode 100644 index 0000000..9965239 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/entity/SysUser.java @@ -0,0 +1,352 @@ +package com.vetti.common.core.domain.entity; + +import java.util.Date; +import java.util.List; +import javax.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.annotation.Excel.Type; +import com.vetti.common.annotation.Excels; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.xss.Xss; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Excel(name = "部门编号", type = Type.IMPORT) + private Long deptId; + + /** 用户账号 */ + @Excel(name = "登录名称") + private String userName; + + /** 用户昵称 */ + @Excel(name = "用户名称") + private String nickName; + + /** 用户邮箱 */ + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Excel(name = "手机号码", cellType = ColumnType.TEXT) + private String phonenumber; + + /** 用户性别 */ + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + private String avatar; + + /** 密码 */ + private String password; + + /** 账号状态(0正常 1停用) */ + @Excel(name = "账号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + @Excel(name = "最后登录IP", type = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 密码最后更新时间 */ + private Date pwdUpdateDate; + + /** + * 用户类型(operation:运营管理,fleetManagement:车队管理) + */ + private String sysUserType; + + /** 部门对象 */ + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + /** 角色对象 */ + private List roles; + + /** 角色组 */ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + /** 角色ID */ + private Long roleId; + + public SysUser() + { + + } + + public SysUser(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public boolean isAdmin() + { + return isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhonenumber() + { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; + } + + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getAvatar() + { + return avatar; + } + + public void setAvatar(String avatar) + { + this.avatar = avatar; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getLoginIp() + { + return loginIp; + } + + public void setLoginIp(String loginIp) + { + this.loginIp = loginIp; + } + + public Date getLoginDate() + { + return loginDate; + } + + public void setLoginDate(Date loginDate) + { + this.loginDate = loginDate; + } + + public Date getPwdUpdateDate() + { + return pwdUpdateDate; + } + + public void setPwdUpdateDate(Date pwdUpdateDate) + { + this.pwdUpdateDate = pwdUpdateDate; + } + + public SysDept getDept() + { + return dept; + } + + public void setDept(SysDept dept) + { + this.dept = dept; + } + + public List getRoles() + { + return roles; + } + + public void setRoles(List roles) + { + this.roles = roles; + } + + public Long[] getRoleIds() + { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) + { + this.roleIds = roleIds; + } + + public Long[] getPostIds() + { + return postIds; + } + + public void setPostIds(Long[] postIds) + { + this.postIds = postIds; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + + public String getSysUserType() { + return sysUserType; + } + + public void setSysUserType(String sysUserType) { + this.sysUserType = sysUserType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("pwdUpdateDate", getPwdUpdateDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("dept", getDept()) + .toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppBody.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppBody.java new file mode 100644 index 0000000..4e26906 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppBody.java @@ -0,0 +1,54 @@ +package com.vetti.common.core.domain.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 用户登录对象 + * + * @author wangxiangshun + */ +@Data +public class LoginAppBody +{ + + /** + * 用户邮箱 + */ + @ApiModelProperty("用户邮箱") + private String email; + + /** + * 登录密码 + */ + @ApiModelProperty("登录密码") + private String passWord; + + /** + * 验证码 + */ + @ApiModelProperty("验证码") + private String code; + + /** + * 一键登录token + */ + @ApiModelProperty("一键登录token") + private String accesstoken; + + /** + * 登录方式 1.邮箱登录 2.一键登录 + */ + @ApiModelProperty("登录方式 1.邮箱登录 2.一键登录") + private String loginType; + + /** + * 平台类型(google,apple) + */ + @ApiModelProperty("平台类型(google,apple)") + private String platform; + + @ApiModelProperty("apple 一键登录(你的应用Bundle ID) / Google应用客户端ID") + private String clientId; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppRegisterBody.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppRegisterBody.java new file mode 100644 index 0000000..cb00a58 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppRegisterBody.java @@ -0,0 +1,39 @@ +package com.vetti.common.core.domain.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 用户注册对象 + * + * @author wangxiangshun + */ +@Data +public class LoginAppRegisterBody { + + /** + * 昵称 + */ + @ApiModelProperty("昵称") + private String name; + + /** + * 用户邮箱 + */ + @ApiModelProperty("用户邮箱") + private String email; + + /** + * 登录密码 + */ + @ApiModelProperty("登录密码") + private String passWord; + + /** + * 验证码 + */ + @ApiModelProperty("验证码") + private String code; + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppResetBody.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppResetBody.java new file mode 100644 index 0000000..f461d39 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppResetBody.java @@ -0,0 +1,38 @@ +package com.vetti.common.core.domain.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 用户密码重置 对象 + * + * @author wangxiangshun + */ +@Data +public class LoginAppResetBody { + + /** + * 用户邮箱 + */ + @ApiModelProperty("用户邮箱") + private String email; + + /** + * 登录密码 + */ + @ApiModelProperty("新登录密码") + private String newPassWord; + + + /** + * 登录密码 + */ + @ApiModelProperty("确认登录密码") + private String confirmPassWord; + + /** + * 验证码 + */ + @ApiModelProperty("验证码") + private String code; +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppSendCode.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppSendCode.java new file mode 100644 index 0000000..daa86c9 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginAppSendCode.java @@ -0,0 +1,20 @@ +package com.vetti.common.core.domain.model; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * APP发送验证码登录对象 + * + * @author wangxiangshun + */ +@Data +public class LoginAppSendCode { + + /** + * 用户邮箱 + */ + @ApiModelProperty("用户邮箱") + private String email; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginBody.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginBody.java new file mode 100644 index 0000000..9f1e325 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginBody.java @@ -0,0 +1,69 @@ +package com.vetti.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +public class LoginBody +{ + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginUser.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginUser.java new file mode 100644 index 0000000..c4b15d6 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/LoginUser.java @@ -0,0 +1,275 @@ +package com.vetti.common.core.domain.model; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.vetti.common.core.domain.entity.SysUser; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Collection; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author ruoyi + */ +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String username; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/domain/model/RegisterBody.java b/vetti-common/src/main/java/com/vetti/common/core/domain/model/RegisterBody.java new file mode 100644 index 0000000..0a6f090 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.vetti.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/page/PageDomain.java b/vetti-common/src/main/java/com/vetti/common/core/page/PageDomain.java new file mode 100644 index 0000000..67de275 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.vetti.common.core.page; + +import com.vetti.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author ruoyi + */ +public class PageDomain +{ + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() + { + return pageNum; + } + + public void setPageNum(Integer pageNum) + { + this.pageNum = pageNum; + } + + public Integer getPageSize() + { + return pageSize; + } + + public void setPageSize(Integer pageSize) + { + this.pageSize = pageSize; + } + + public String getOrderByColumn() + { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) + { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() + { + return isAsc; + } + + public void setIsAsc(String isAsc) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/page/TableAppDataInfo.java b/vetti-common/src/main/java/com/vetti/common/core/page/TableAppDataInfo.java new file mode 100644 index 0000000..be6123a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/page/TableAppDataInfo.java @@ -0,0 +1,122 @@ +package com.vetti.common.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +public class TableAppDataInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 总记录数 + */ + private long total; + + /** + * 列表数据 + */ + private List data; + + /** + * 当前页码 + */ + private long pageNum; + + /** + * 总页数 + */ + private long pages; + + /** + * 是否有下一页 + */ + private boolean hasNextPage; + +// /** +// * 消息状态码 +// */ +// private int code; +// +// /** +// * 消息内容 +// */ +// private String msg = "查询成功"; + + /** + * 表格数据对象 + */ + public TableAppDataInfo() { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableAppDataInfo(List list, int total) { + this.data = list; + this.total = total; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public long getPageNum() { + return pageNum; + } + + public void setPageNum(long pageNum) { + this.pageNum = pageNum; + } + + public long getPages() { + return pages; + } + + public void setPages(long pages) { + this.pages = pages; + } + + public boolean isHasNextPage() { + return hasNextPage; + } + + public void setHasNextPage(boolean hasNextPage) { + this.hasNextPage = hasNextPage; + } + // public int getCode() { +// return code; +// } +// +// public void setCode(int code) { +// this.code = code; +// } +// +// public String getMsg() { +// return msg; +// } +// +// public void setMsg(String msg) { +// this.msg = msg; +// } +} + diff --git a/vetti-common/src/main/java/com/vetti/common/core/page/TableDataInfo.java b/vetti-common/src/main/java/com/vetti/common/core/page/TableDataInfo.java new file mode 100644 index 0000000..30e2af7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.vetti.common.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 列表数据 */ + private List rows; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, long total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/page/TablePageDataInfo.java b/vetti-common/src/main/java/com/vetti/common/core/page/TablePageDataInfo.java new file mode 100644 index 0000000..53e8f71 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/page/TablePageDataInfo.java @@ -0,0 +1,71 @@ +package com.vetti.common.core.page; + +import java.io.Serializable; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +public class TablePageDataInfo implements Serializable { + + + private static final long serialVersionUID = 1L; + + /** + * 消息状态码 + */ + private int code; + + /** + * 消息内容 + */ + private String msg = "查询成功"; + + /** + * 列表数据 + */ + private TableAppDataInfo data; + + /** + * 表格数据对象 + */ + public TablePageDataInfo() { + + } + + /** + * 分页 + * + * @param data 查询数据 + */ + public TablePageDataInfo(TableAppDataInfo data) { + this.data = data; + } + + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public TableAppDataInfo getData() { + return data; + } + + public void setData(TableAppDataInfo data) { + this.data = data; + } +} + diff --git a/vetti-common/src/main/java/com/vetti/common/core/page/TableSupport.java b/vetti-common/src/main/java/com/vetti/common/core/page/TableSupport.java new file mode 100644 index 0000000..befad71 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.vetti.common.core.page; + +import com.vetti.common.core.text.Convert; +import com.vetti.common.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author ruoyi + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static PageDomain getPageDomain() + { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); + pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public static PageDomain buildPageRequest() + { + return getPageDomain(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/redis/RedisCache.java b/vetti-common/src/main/java/com/vetti/common/core/redis/RedisCache.java new file mode 100644 index 0000000..befed26 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/redis/RedisCache.java @@ -0,0 +1,268 @@ +package com.vetti.common.core.redis; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisCache +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) + { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) + { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) + { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/redis/RedisService.java b/vetti-common/src/main/java/com/vetti/common/core/redis/RedisService.java new file mode 100644 index 0000000..3a6b197 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/redis/RedisService.java @@ -0,0 +1,364 @@ +package com.vetti.common.core.redis; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author wangxiangshun + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisService +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) + { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) + { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) + { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } + + /** + * 原子递增 + * @param key + * @param i + */ + public void increment(String key, int i) { + redisTemplate.opsForValue().increment(key, i); + } + + /** + * 原子递增 + * @param key + * @param i + */ + public void decrement(String key, int i) { + redisTemplate.opsForValue().decrement(key, i); + } + + /** + * 获得key数组里面key2元素的索引 + * @param key + * @param key2 + * @return + */ + public Long rank(String key, Object key2) { + ZSetOperations zset = redisTemplate.opsForZSet(); + return zset.rank(key, key2); + } + + /** + * 有序集合添加 + * @param key + * @param value + * @param scoure + */ + public void zAdd(String key, Object value, double scoure) { + ZSetOperations zset = redisTemplate.opsForZSet(); + zset.add(key, value, scoure); + } + + /** + * 获得key数组里面key2元素的排序值 + * @param key + * @param key2 + * @return + */ + public double score(String key, Object key2) { + ZSetOperations zset = redisTemplate.opsForZSet(); + return zset.score(key, key2); + } + + /** + * 从高到低的排序集中获取从头(start)到尾(end)内的元素。 + * @param key + * @param start + * @param end + * @return + */ + public Set reverseRange(String key, long start, long end) { + ZSetOperations zset = redisTemplate.opsForZSet(); + return zset.reverseRange(key, start, end); + } + + /** + * 根据分数保留指定个数,其余的元素删除 + * @param key + * @param number + * @return + */ + public Boolean remove(String key, Integer number) { + ZSetOperations zset = redisTemplate.opsForZSet(); + Long size = zset.size(key); + if (size > number) { + //获取变量指定区间的元素 + Set> typedTuples = zset.rangeWithScores(key, 0, (size - 1) - number); + Set set = new HashSet(); + assert typedTuples != null; + for (ZSetOperations.TypedTuple o : typedTuples) { + set.add(o.getValue()); + } + for (Object o : set) { + Long aLong = zset.remove(key, o); + if (aLong == null) { + return false; + } + } + return true; + }else { + return true; + } + } + + /** + * 从 Redis List 中移除多个元素 + * @param key Redis 键 + * @param elementsToRemove 要移除的元素列表 + */ + public void removeElementsFromList(String key, List elementsToRemove) { + for (String element : elementsToRemove) { + redisTemplate.opsForList().remove(key, 0, element); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/service/BaseServiceImpl.java b/vetti-common/src/main/java/com/vetti/common/core/service/BaseServiceImpl.java new file mode 100644 index 0000000..9eab1f0 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/service/BaseServiceImpl.java @@ -0,0 +1,99 @@ +package com.vetti.common.core.service; + +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.core.domain.model.LoginUser; + +import java.io.File; +import java.util.Date; +import java.util.UUID; + +import static com.vetti.common.utils.SecurityUtils.getLoginUser; + +/** + * 基础业务 + * + * @author ruoyi + */ +public abstract class BaseServiceImpl { + + /** + * 系统共通字段-填充 + */ + protected void fill(String fillType, BaseEntity baseEntity) { + Date currentDate = new Date(); + LoginUser loginUser = getLoginUser(); + String currentUserId = ""; + Long deptId = 0L; + if(loginUser != null){ + currentUserId = String.valueOf(loginUser.getUsername()); + if (currentUserId == null || "".equals(currentUserId)) { + currentUserId = "system"; + } + deptId = loginUser.getDeptId(); + } + switch (fillType) { + case "INSERT": + baseEntity.setCreateTime(currentDate); + baseEntity.setCreateBy(currentUserId); + baseEntity.setUpdateTime(currentDate); + baseEntity.setUpdateBy(currentUserId); + baseEntity.setDelFlag(Constants.DEL_FLAG_ZERO); +// baseEntity.setVersionNo(0L); + break; + case "UPDATE": + baseEntity.setUpdateTime(currentDate); + baseEntity.setUpdateBy(currentUserId); + break; + default: + } + } + + /** + * 系统共通字段-填充 + */ + protected void fillJob(String fillType, BaseEntity baseEntity) { + Date currentDate = new Date(); + String currentUserId = "system"; + switch (fillType) { + case "INSERT": + baseEntity.setCreateTime(currentDate); + baseEntity.setCreateBy(currentUserId); + baseEntity.setUpdateTime(currentDate); + baseEntity.setUpdateBy(currentUserId); + baseEntity.setDelFlag(Constants.DEL_FLAG_ZERO); +// baseEntity.setVersionNo(0L); + break; + case "UPDATE": + baseEntity.setUpdateTime(currentDate); + baseEntity.setUpdateBy(currentUserId); + break; + default: + } + } + + /** + * 编码文件名 + */ + protected String encodingFilename(String filename) { + filename = UUID.randomUUID().toString() + "_" + filename + ".xls"; + return filename; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + protected String getAbsoluteFile(String filename) { + String downloadPath = RuoYiConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/text/CharsetKit.java b/vetti-common/src/main/java/com/vetti/common/core/text/CharsetKit.java new file mode 100644 index 0000000..0212673 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.vetti.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.vetti.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @author ruoyi + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/text/Convert.java b/vetti-common/src/main/java/com/vetti/common/core/text/Convert.java new file mode 100644 index 0000000..8518c97 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/text/Convert.java @@ -0,0 +1,1018 @@ +package com.vetti.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; +import com.vetti.common.utils.StringUtils; + +/** + * 类型转换器 + * + * @author ruoyi + */ +public class Convert +{ + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + if (StringUtils.isEmpty(str)) + { + return new String[] {}; + } + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no、1、0、是、否, 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + case "yes": + case "ok": + case "1": + case "是": + return true; + case "false": + case "no": + case "0": + case "否": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) + { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Enum.valueOf(clazz, valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[] || obj instanceof Byte[]) + { + if (obj instanceof byte[]) + { + return str((byte[]) obj, charset); + } + else + { + Byte[] bytes = (Byte[]) obj; + int length = bytes.length; + byte[] dest = new byte[length]; + for (int i = 0; i < length; i++) + { + dest[i] = bytes[i]; + } + return str(dest, charset); + } + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char[] c = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + return new String(c); + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + // 优化double计算精度丢失问题 + BigDecimal nNum = new BigDecimal(n); + BigDecimal decimal = new BigDecimal(10); + BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN); + double d = scale.doubleValue(); + s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/core/text/StrFormatter.java b/vetti-common/src/main/java/com/vetti/common/core/text/StrFormatter.java new file mode 100644 index 0000000..7915430 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.vetti.common.core.text; + +import com.vetti.common.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author ruoyi + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapCoordinates.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapCoordinates.java new file mode 100644 index 0000000..8231177 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapCoordinates.java @@ -0,0 +1,19 @@ +package com.vetti.common.entity.hereMap; + +import lombok.Data; + +/** + * 坐标 + * + * @author ID + * @date 2025/9/11 15:14 + */ +@Data +public class HereMapCoordinates { + + private double latitude; + private double longitude; + private String address; + private String latlng; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapLocationDto.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapLocationDto.java new file mode 100644 index 0000000..9ec3e42 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapLocationDto.java @@ -0,0 +1,38 @@ +package com.vetti.common.entity.hereMap; + +import lombok.Data; + +/** + * @author ID + * @date 2025/9/1 21:56 + */ +@Data +public class HereMapLocationDto { + private String title; + private String address; + private String city; + private String country; + private String postalCode; + private double latitude; + private double longitude; + + public HereMapLocationDto() { + } + + + public HereMapLocationDto(String address, String city, String country, String postalCode, double latitude, double longitude) { + this.address = address; + this.city = city; + this.country = country; + this.postalCode = postalCode; + this.latitude = latitude; + this.longitude = longitude; + } + + public HereMapLocationDto(String title, String address, double latitude, double longitude) { + this.title = title; + this.address = address; + this.latitude = latitude; + this.longitude = longitude; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapRouteVo.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapRouteVo.java new file mode 100644 index 0000000..95e7e7b --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapRouteVo.java @@ -0,0 +1,35 @@ +package com.vetti.common.entity.hereMap; + +import com.vetti.common.entity.hereMap.vehicle.HereMapVehicleTruck; +import lombok.Data; + +import java.util.List; + +/** + * @author ID + * @date 2025/9/4 16:40 + */ +@Data +public class HereMapRouteVo { + + // 基本路线参数 + private HereMapCoordinates origin; // 起点坐标 (纬度,经度) + private HereMapCoordinates destination; // 终点坐标 (纬度,经度) + private List via; // 途径点坐标列表 (纬度,经度) + private String transportMode; // 运输方式 + private List avoid; // 避开选项 + + private String routingMode; // 路线偏好 + + private String returnStr = "polyline,actions,instructions,summary,travelSummary,typicalDuration,turnByTurnActions,elevation,routeHandle,passthrough,incidents,routingZones,truckRoadTypes,tolls,routeLabels,potentialTimeDependentViolations,noThroughRestrictions"; // 路线偏好 + private String lang; // 语言 + private String units; // 单位("metric" "imperial" 枚举:“公制”“英制”) + + private String departureTime; // 开始时间 + private String arrivalTime; // 结束时间 + + + private HereMapVehicleTruck vehicle; + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapTruckRoute.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapTruckRoute.java new file mode 100644 index 0000000..079daaa --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/HereMapTruckRoute.java @@ -0,0 +1,43 @@ +package com.vetti.common.entity.hereMap; + +import lombok.Data; + +/** + * @author ID + * @date 2025/9/4 16:40 + */ +@Data +public class HereMapTruckRoute { + + // 基本路线参数 + private String start; // 起点坐标 (纬度,经度) + private String end; // 终点坐标 (纬度,经度) + private String routePreference; // 路线偏好: fastest, shortest, balanced + + // 车辆基本参数 + private Integer height; // 高度(厘米) + private Integer width; // 宽度(厘米) + private Integer length; // 长度(厘米) + private Integer weightTotal; // 总重量(KG) + + // 避开选项 + private boolean avoidHighways; // 避开高速公路 + private boolean avoidTolls; // 避开收费道路 + private boolean avoidFerries; // 避开轮渡 + private boolean avoidTunnels; // 避开隧道 + private boolean avoidDifficultTurns; // 避开掉头 + private boolean avoidDirtRoads; // 避开土路 + + // 危化品参数 + private boolean hazmatExplosive; + private boolean hazmatGas; + private boolean hazmatFlammable; + private boolean hazmatCombustible; + private boolean hazmatOrganic; + private boolean hazmatPoison; + private boolean hazmatRadioactive; + private boolean hazmatCorrosive; + private boolean hazmatPoisonousInhalation; + private boolean hazmatHarmfulToWater; + private boolean hazmatOther; +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/BaseHereMapQueryParamVehicle.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/BaseHereMapQueryParamVehicle.java new file mode 100644 index 0000000..7c27093 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/BaseHereMapQueryParamVehicle.java @@ -0,0 +1,38 @@ +package com.vetti.common.entity.hereMap.queryParam; + +/** + * @author ID + * @date 2025/9/5 23:55 + */ +public abstract class BaseHereMapQueryParamVehicle { + + private Integer height;//高 cm + + private Integer width;//宽 cm + + private Integer length;//长 cm + + private Integer payloadCapacity;//载重 kg + + private String shippedHazardousGoods;//危化品 + + protected HereMapQueryParam getHeight(Integer height) { + return HereMapQueryParam.build("vehicle[height]", height); + } + + protected HereMapQueryParam getWidth(Integer width) { + return HereMapQueryParam.build("vehicle[width]", width); + } + + protected HereMapQueryParam getLength(Integer length) { + return HereMapQueryParam.build("vehicle[length]", length); + } + + protected HereMapQueryParam getPayloadCapacity(Integer payloadCapacity) { + return HereMapQueryParam.build("vehicle[payloadCapacity]", payloadCapacity); + } + + protected HereMapQueryParam getShippedHazardousGoods(String shippedHazardousGoods) { + return HereMapQueryParam.build("vehicle[shippedHazardousGoods]", shippedHazardousGoods); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParam.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParam.java new file mode 100644 index 0000000..67a6999 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParam.java @@ -0,0 +1,22 @@ +package com.vetti.common.entity.hereMap.queryParam; + +import lombok.Data; + +/** + * @author ID + * @date 2025/9/4 22:03 + */ +@Data +public class HereMapQueryParam { + + private String key; + private Object value; + + public static HereMapQueryParam build(String key, Object value) { + HereMapQueryParam data = new HereMapQueryParam(); + data.setKey(key); + data.setValue(value); + return data; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParamTruck.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParamTruck.java new file mode 100644 index 0000000..b410e7f --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/queryParam/HereMapQueryParamTruck.java @@ -0,0 +1,29 @@ +package com.vetti.common.entity.hereMap.queryParam; + +/** + * @author ID + * @date 2025/9/5 23:55 + */ +public class HereMapQueryParamTruck extends BaseHereMapQueryParamVehicle{ + + public HereMapQueryParam getHeight(Integer height) { + return super.getHeight(height); + } + + public HereMapQueryParam getWidth(Integer width) { + return super.getWidth(width); + } + + public HereMapQueryParam getLength(Integer length) { + return super.getLength(length); + } + + public HereMapQueryParam getPayloadCapacity(Integer payloadCapacity) { + return super.getPayloadCapacity(payloadCapacity); + } + + public HereMapQueryParam getShippedHazardousGoods(String shippedHazardousGoods) { + return super.getShippedHazardousGoods(shippedHazardousGoods); + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapAction.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapAction.java new file mode 100644 index 0000000..576bde6 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapAction.java @@ -0,0 +1,43 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 基础导航动作(含中文指令) + * + * @author ID + * @date 2025/9/11 14:44 + */ +@Data +public class HereMapAction { + + /** + * 动作类型(depart/turn/continue/arrive) + */ + private String action; + /** + * 动作耗时(秒) + */ + private Integer duration; + /** + * 动作距离(米) + */ + private Integer length; + /** + * 中文导航指令(如"沿着 Bluegum Pl 朝 Alison St 行驶") + */ + private String instruction; + /** + * 偏移量(路线中的位置索引) + */ + private Integer offset; + /** + * 转向方向(仅turn动作有值:left/right) + */ + private String direction; + /** + * 转向强度(仅turn动作有值:quite) + */ + private String severity; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapArrival.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapArrival.java new file mode 100644 index 0000000..3a802cc --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapArrival.java @@ -0,0 +1,23 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 到达信息 + * + * @author ID + * @date 2025/9/11 14:55 + */ +@Data +public class HereMapArrival { + + /** + * 到达时间(ISO8601格式,如"2025-09-11T11:44:37+10:00") + */ + private String time; + /** + * 到达地点 + */ + private HereMapPlace place; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapDeparture.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapDeparture.java new file mode 100644 index 0000000..ab363d7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapDeparture.java @@ -0,0 +1,23 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 出发信息 + * + * @author ID + * @date 2025/9/11 14:54 + */ +@Data +public class HereMapDeparture { + + /** + * 出发时间(ISO8601格式,如"2025-09-11T11:27:17+10:00") + */ + private String time; + /** + * 出发地点 + */ + private HereMapPlace place; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapLocation.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapLocation.java new file mode 100644 index 0000000..0a7b336 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapLocation.java @@ -0,0 +1,27 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 坐标信息(经纬度、海拔) + * + * @author ID + * @date 2025/9/11 14:57 + */ +@Data +public class HereMapLocation { + + /** + * 纬度(如-33.7895301) + */ + private Double lat; + /** + * 经度(如151.16786) + */ + private Double lng; + /** + * 海拔(米,如70.0) + */ + private Double elv; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapNotice.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapNotice.java new file mode 100644 index 0000000..223f2f4 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapNotice.java @@ -0,0 +1,27 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 通知信息(如计算提示、警告) + * + * @author ID + * @date 2025/9/11 15:02 + */ +@Data +public class HereMapNotice { + + /** + * 通知标题(如"Pre-conditions required for mlDuration calculation failed") + */ + private String title; + /** + * 通知编码(如"mlDurationUnavailable") + */ + private String code; + /** + * 通知级别(info/warn/error,此处为info) + */ + private String severity; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapPlace.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapPlace.java new file mode 100644 index 0000000..3b7dcf0 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapPlace.java @@ -0,0 +1,27 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 地点信息(经纬度、海拔) + * + * @author ID + * @date 2025/9/11 14:56 + */ +@Data +public class HereMapPlace { + + /** + * 地点类型(固定为"place") + */ + private String type; + /** + * 校正后坐标(含海拔) + */ + private HereMapLocation location; + /** + * 原始坐标(用户输入或初始定位) + */ + private HereMapLocation originalLocation; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadInfo.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadInfo.java new file mode 100644 index 0000000..6f86b0b --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadInfo.java @@ -0,0 +1,29 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +import java.util.List; + +/** + * 道路信息(名称、编号、行驶方向) + * + * @author ID + * @date 2025/9/11 14:47 + */ +@Data +public class HereMapRoadInfo { + + /** + * 道路名称列表(多语言,此处仅en) + */ + private List name; + /** + * 道路编号列表(如A1、A38,含路线类型) + */ + private List number; + /** + * 行驶方向列表(如"Frenchs Forest"、"City") + */ + private List toward; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadName.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadName.java new file mode 100644 index 0000000..f340054 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadName.java @@ -0,0 +1,23 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 道路名称(多语言支持) + * + * @author ID + * @date 2025/9/11 14:48 + */ +@Data +public class HereMapRoadName { + + /** + * 道路名称(如"Bluegum Pl") + */ + private String value; + /** + * 语言(固定为"en") + */ + private String language; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadNumber.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadNumber.java new file mode 100644 index 0000000..f4df0e2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadNumber.java @@ -0,0 +1,27 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 道路编号(含路线类型) + * + * @author ID + * @date 2025/9/11 14:49 + */ +@Data +public class HereMapRoadNumber { + + /** + * 道路编号(如"A1"、"A38") + */ + private String value; + /** + * 语言(固定为"en") + */ + private String language; + /** + * 路线类型(固定为6,代表主干道) + */ + private Integer routeType; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadToward.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadToward.java new file mode 100644 index 0000000..165ade5 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoadToward.java @@ -0,0 +1,23 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 道路行驶方向 + * + * @author ID + * @date 2025/9/11 14:50 + */ +@Data +public class HereMapRoadToward { + + /** + * 方向名称(如"Northbridge"、"Airport") + */ + private String value; + /** + * 语言(固定为"en") + */ + private String language; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoute.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoute.java new file mode 100644 index 0000000..bd56c44 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRoute.java @@ -0,0 +1,33 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +import java.util.List; + +/** + * 单个路由信息 + * + * @author ID + * @date 2025/9/11 14:42 + */ +@Data +public class HereMapRoute { + + /** + * 路由ID(如"6e1a8f39-d309-43d0-8b72-ab9b810af8b1") + */ + private String id; + /** + * 路由分段列表(仅包含vehicle类型分段) + */ + private List sections; + /** + * 路由标识(如道路名称、编号) + */ + private List routeLabels; + /** + * 路由句柄(编码字符串) + */ + private String routeHandle; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteDto.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteDto.java new file mode 100644 index 0000000..2434414 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteDto.java @@ -0,0 +1,21 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +import java.util.List; + +/** + * 路由响应顶层实体 + * + * @author ID + * @date 2025/9/11 14:41 + */ +@Data +public class HereMapRouteDto { + + /** + * 路由列表(JSON中的"routes"数组) + */ + private List routes; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteLabel.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteLabel.java new file mode 100644 index 0000000..7407134 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteLabel.java @@ -0,0 +1,27 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 路由标识(道路名称、编号) + * + * @author ID + * @date 2025/9/11 14:43 + */ +@Data +public class HereMapRouteLabel { + + /** + * 标识类型(Name/RouteNumber) + */ + private String label_type; + /** + * 名称信息(如道路名称) + */ + private HereMapRoadName name; + /** + * 路线编号信息(如A38) + */ + private HereMapRoadNumber routeNumber; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteSection.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteSection.java new file mode 100644 index 0000000..ae7db64 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapRouteSection.java @@ -0,0 +1,65 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +import java.util.List; + +/** + * 路由分段(车辆行驶分段) + * + * @author ID + * @date 2025/9/11 14:42 + */ +@Data +public class HereMapRouteSection { + + /** + * 分段ID(如"7bed8aae-2ad3-4e74-8a11-0a2ae7eedc97") + */ + private String id; + /** + * 分段类型(固定为"vehicle") + */ + private String type; + /** + * 行驶动作列表(含中文导航指令) + */ + private List actions; + /** + * 详细转向动作列表(含道路信息、转向角度) + */ + private List turnByTurnActions; + /** + * 出发信息(时间、地点) + */ + private HereMapDeparture departure; + /** + * 到达信息(时间、地点) + */ + private HereMapArrival arrival; + /** + * 分段概要(时长、距离等) + */ + private HereMapSummary summary; + /** + * 行程概要(与summary结构一致) + */ + private HereMapTravelSummary travelSummary; + /** + * 路线polyline编码(用于地图绘制) + */ + private String polyline; + /** + * 通知信息(如计算提示) + */ + private List notices; + /** + * 语言(固定为"zh-cn") + */ + private String language; + /** + * 交通方式(固定为"car") + */ + private HereMapTransport transport; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpost.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpost.java new file mode 100644 index 0000000..5b8158b --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpost.java @@ -0,0 +1,21 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +import java.util.List; + +/** + * 路牌信息(导航标识) + * + * @author ID + * @date 2025/9/11 14:52 + */ +@Data +public class HereMapSignpost { + + /** + * 路牌标签列表(含道路名称、方向、编号) + */ + private List labels; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpostLabel.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpostLabel.java new file mode 100644 index 0000000..75ce7a9 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSignpostLabel.java @@ -0,0 +1,23 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 路牌标签(单个标识项) + * + * @author ID + * @date 2025/9/11 14:52 + */ +@Data +public class HereMapSignpostLabel { + + /** + * 名称标签(如道路名称、方向) + */ + private HereMapRoadName name; + /** + * 路线编号标签(如"A38") + */ + private HereMapRoadNumber routeNumber; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSummary.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSummary.java new file mode 100644 index 0000000..7d6f24b --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapSummary.java @@ -0,0 +1,31 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 分段概要(时长、距离统计) + * + * @author ID + * @date 2025/9/11 14:59 + */ +@Data +public class HereMapSummary { + + /** + * 总耗时(秒,含交通延误) + */ + private Integer duration; + /** + * 总距离(米) + */ + private Integer length; + /** + * 基础耗时(秒,不含交通延误) + */ + private Integer baseDuration; + /** + * 典型耗时(秒,历史平均耗时) + */ + private Integer typicalDuration; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTransport.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTransport.java new file mode 100644 index 0000000..71efcc9 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTransport.java @@ -0,0 +1,19 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 交通方式 + * + * @author ID + * @date 2025/9/11 15:03 + */ +@Data +public class HereMapTransport { + + /** + * 交通模式(固定为"car") + */ + private String mode; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTravelSummary.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTravelSummary.java new file mode 100644 index 0000000..9acf5a2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTravelSummary.java @@ -0,0 +1,31 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 行程概要(与Summary结构完全一致) + * + * @author ID + * @date 2025/9/11 15:00 + */ +@Data +public class HereMapTravelSummary { + + /** + * 总耗时(秒,含交通延误) + */ + private Integer duration; + /** + * 总距离(米) + */ + private Integer length; + /** + * 基础耗时(秒,不含交通延误) + */ + private Integer baseDuration; + /** + * 典型耗时(秒,历史平均耗时) + */ + private Integer typicalDuration; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTurnByTurnAction.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTurnByTurnAction.java new file mode 100644 index 0000000..98634fe --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/route/HereMapTurnByTurnAction.java @@ -0,0 +1,55 @@ +package com.vetti.common.entity.hereMap.route; + +import lombok.Data; + +/** + * 详细转向动作(含道路信息、转向角度) + * + * @author ID + * @date 2025/9/11 14:46 + */ +@Data +public class HereMapTurnByTurnAction { + + /** + * 动作类型(同Action的action字段) + */ + private String action; + /** + * 动作耗时(秒) + */ + private Integer duration; + /** + * 动作距离(米) + */ + private Integer length; + /** + * 偏移量(路线中的位置索引) + */ + private Integer offset; + /** + * 转向方向(仅turn动作有值:left/right) + */ + private String direction; + /** + * 转向强度(仅turn动作有值:quite) + */ + private String severity; + /** + * 当前道路信息(仅非depart动作有值) + */ + private HereMapRoadInfo currentRoad; + /** + * 下一条道路信息(仅非arrive动作有值) + */ + private HereMapRoadInfo nextRoad; + /** + * 转向角度(度,负值为左转,正值为右转) + */ + private Double turnAngle; + /** + * 路牌信息(含导航标识) + */ + private HereMapSignpost signpost; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicle.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicle.java new file mode 100644 index 0000000..ae3804a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicle.java @@ -0,0 +1,23 @@ +package com.vetti.common.entity.hereMap.vehicle; + +import lombok.Data; + +/** + * @author ID + * @date 2025/9/12 9:33 + */ +@Data +public class HereMapVehicle { + + private Integer height;//高 cm + + private Integer width;//宽 cm + + private Integer length;//长 cm + + private Double speedCap;//最大速度 m/s + + private Integer grossWeight;//车辆总重量,包括挂车和满载货物。 kg + + private Integer currentWeight;//当前车辆总重量,包括挂车和满载货物。 kg +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicleTruck.java b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicleTruck.java new file mode 100644 index 0000000..b817b41 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/hereMap/vehicle/HereMapVehicleTruck.java @@ -0,0 +1,24 @@ +package com.vetti.common.entity.hereMap.vehicle; + +import lombok.Data; + +import java.util.List; + +/** + * @author ID + * @date 2025/9/12 9:33 + */ +@Data +public class HereMapVehicleTruck extends HereMapVehicle { + + private Integer axleCount;//总轴数 指定车辆具有的轴的总数,即基础车辆上的轴和任何附加的拖车。 + private Integer trailerAxleCount;//拖车总轴数,不含车头 指定连接到车辆的所有拖车的轴总数。 这个数字包含在 axleCount 中,因此 trailerAxleCount 必须严格小于 axleCount 。 trailerCount 必须非零。 + private Integer weightPerAxle;//每轴最重车辆重量 kg 每轴最重车辆重量,单位为公斤。 + private Integer trailerCount;//拖车数量 与车辆相连的拖车的数量。 + private String type;//类型 StraightTruck:直车(单车架设计,载货区(如货箱)与车架永久固定,不可分离);Tractor:牵引车 / 拖拉机()仅为 “牵引车头”,无独立载货区,需通过牵引装置连接半挂车 + + private String cargoType;//货物类型 非here map 属性 normal:普通货物;hazardous:危险品 + private List hazardousGoods;//危化品 + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/verification/BaseTemplateEmail.java b/vetti-common/src/main/java/com/vetti/common/entity/verification/BaseTemplateEmail.java new file mode 100644 index 0000000..8a3c45f --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/verification/BaseTemplateEmail.java @@ -0,0 +1,17 @@ +package com.vetti.common.entity.verification; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +/** + * @author ID + * @date 2025/8/29 21:42 + */ +public class BaseTemplateEmail { + + public JsonObject toJsonObject() { + return new Gson().toJsonTree(this).getAsJsonObject(); + } + + +} diff --git a/vetti-common/src/main/java/com/vetti/common/entity/verification/RoutezVerificationCodeTemplate.java b/vetti-common/src/main/java/com/vetti/common/entity/verification/RoutezVerificationCodeTemplate.java new file mode 100644 index 0000000..e91bee2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/entity/verification/RoutezVerificationCodeTemplate.java @@ -0,0 +1,15 @@ +package com.vetti.common.entity.verification; + +import lombok.Data; + +/** + * @author ID + * @date 2025/8/29 21:42 + */ +@Data +public class RoutezVerificationCodeTemplate extends BaseTemplateEmail { + + private String verification_code; + private int verification_expiration; + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/BusinessStatus.java b/vetti-common/src/main/java/com/vetti/common/enums/BusinessStatus.java new file mode 100644 index 0000000..60cb3f6 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.vetti.common.enums; + +/** + * 操作状态 + * + * @author ruoyi + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/BusinessType.java b/vetti-common/src/main/java/com/vetti/common/enums/BusinessType.java new file mode 100644 index 0000000..b204bc3 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.vetti.common.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/DataSourceType.java b/vetti-common/src/main/java/com/vetti/common/enums/DataSourceType.java new file mode 100644 index 0000000..d4f7e7c --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.vetti.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/DesensitizedType.java b/vetti-common/src/main/java/com/vetti/common/enums/DesensitizedType.java new file mode 100644 index 0000000..6d0f7fb --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/DesensitizedType.java @@ -0,0 +1,59 @@ +package com.vetti.common.enums; + +import java.util.function.Function; +import com.vetti.common.utils.DesensitizedUtil; + +/** + * 脱敏类型 + * + * @author ruoyi + */ +public enum DesensitizedType +{ + /** + * 姓名,第2位星号替换 + */ + USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")), + + /** + * 密码,全部字符都用*代替 + */ + PASSWORD(DesensitizedUtil::password), + + /** + * 身份证,中间10位星号替换 + */ + ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")), + + /** + * 手机号,中间4位星号替换 + */ + PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), + + /** + * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换 + */ + EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")), + + /** + * 银行卡号,保留最后4位,其他星号替换 + */ + BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")), + + /** + * 车牌号码,包含普通车辆、新能源车辆 + */ + CAR_LICENSE(DesensitizedUtil::carLicense); + + private final Function desensitizer; + + DesensitizedType(Function desensitizer) + { + this.desensitizer = desensitizer; + } + + public Function desensitizer() + { + return desensitizer; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/FillTypeEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/FillTypeEnum.java new file mode 100644 index 0000000..9c71a0e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/FillTypeEnum.java @@ -0,0 +1,30 @@ +package com.vetti.common.enums; + +/** + * 填充类型CODE + * @author wangxiangshun + */ +public enum FillTypeEnum { + + INSERT("INSERT", "新增"), UPDATE("UPDATE", "修改"); + + private final String code; + private final String info; + + FillTypeEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/HttpMethod.java b/vetti-common/src/main/java/com/vetti/common/enums/HttpMethod.java new file mode 100644 index 0000000..3776f8e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.vetti.common.enums; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @author ruoyi + */ +public enum HttpMethod +{ + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + private static final Map mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/InternationalLangTypeEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/InternationalLangTypeEnum.java new file mode 100644 index 0000000..87556e2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/InternationalLangTypeEnum.java @@ -0,0 +1,31 @@ +package com.vetti.common.enums; +/** + * 国际化语言类型编号 + * @author wangxiangshun + */ +public enum InternationalLangTypeEnum { + + INTERNATIONAL_LANG_TYPE_ENUM_ZH("zh_CN", "中文"), + INTERNATIONAL_LANG_TYPE_ENUM_EN("en_US", "英文"), + ; + + private final String code; + private final String info; + + InternationalLangTypeEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/LanguageTypeEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/LanguageTypeEnum.java new file mode 100644 index 0000000..c4d78ef --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/LanguageTypeEnum.java @@ -0,0 +1,41 @@ +package com.vetti.common.enums; +/** + * 国际化语言类型枚举 + * @author Wangxiangshun + */ +public enum LanguageTypeEnum { + + ZH("zh", "CN"), + EN("en", "US"), + JA("ja", "JP"), + ; + + private final String code; + private final String info; + + LanguageTypeEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + + public static LanguageTypeEnum getByInfo(String code) { + LanguageTypeEnum[] statusEnums = values(); + for (LanguageTypeEnum statusEnum : statusEnums) { + if (statusEnum.getCode().equals(code)) { + return statusEnum; + } + } + return null; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/LimitType.java b/vetti-common/src/main/java/com/vetti/common/enums/LimitType.java new file mode 100644 index 0000000..fe52997 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.vetti.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/OperatorType.java b/vetti-common/src/main/java/com/vetti/common/enums/OperatorType.java new file mode 100644 index 0000000..b55932f --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.vetti.common.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/SerialNumberBusinessType.java b/vetti-common/src/main/java/com/vetti/common/enums/SerialNumberBusinessType.java new file mode 100644 index 0000000..bbe50d9 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/SerialNumberBusinessType.java @@ -0,0 +1,31 @@ +package com.vetti.common.enums; + +/** + * 流水号业务类型 + */ +public enum SerialNumberBusinessType { + + FLEET_INFO("FLEET_INFO", "车队"), + PARTNER("partner", "合作方"), + TASK("task", "任务"), + ; + + private final String code; + private final String info; + + SerialNumberBusinessType(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/UserStatus.java b/vetti-common/src/main/java/com/vetti/common/enums/UserStatus.java new file mode 100644 index 0000000..ef79870 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.vetti.common.enums; + +/** + * 用户状态 + * + * @author ruoyi + */ +public enum UserStatus +{ + OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/AddressTypeEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/AddressTypeEnum.java new file mode 100644 index 0000000..4ef63dc --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/AddressTypeEnum.java @@ -0,0 +1,33 @@ +package com.vetti.common.enums.command; + +/** + * 地址类型 + */ +public enum AddressTypeEnum { + + DEFAULT("default", "Default"), + WORK("work", "Work"), + + + ; + + private final String code; + private final String info; + + AddressTypeEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/CarStatusEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/CarStatusEnum.java new file mode 100644 index 0000000..eee4b3c --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/CarStatusEnum.java @@ -0,0 +1,34 @@ +package com.vetti.common.enums.command; + +/** + * 车辆状态 + */ +public enum CarStatusEnum { + + COMPLIANT("Compliant", "Compliant"), + INSPECTION_DUE("inspectionDue", "InspectionDue"), + SERVICE_REQUIRED("serviceRequired", "ServiceRequired"), + UNCHECKED("unchecked", "Unchecked"), + + ; + + private final String code; + private final String info; + + CarStatusEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/LoginPlatformTypeEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/LoginPlatformTypeEnum.java new file mode 100644 index 0000000..ade08fb --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/LoginPlatformTypeEnum.java @@ -0,0 +1,31 @@ +package com.vetti.common.enums.command; + +/** + * 登录平台类型 + */ +public enum LoginPlatformTypeEnum { + + GOOGLE("google", "谷歌"), + APPLY("apply", "苹果"), + + ; + + private final String code; + private final String info; + + LoginPlatformTypeEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulItemRecordStatusEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulItemRecordStatusEnum.java new file mode 100644 index 0000000..0da6326 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulItemRecordStatusEnum.java @@ -0,0 +1,32 @@ +package com.vetti.common.enums.command; + +/** + * 检修项目记录状态 + */ +public enum OverhaulItemRecordStatusEnum { + + STATUS_0("0", "未通过"), + STATUS_1("1", "通过"), + + ; + + private final String code; + private final String info; + + OverhaulItemRecordStatusEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulStatusEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulStatusEnum.java new file mode 100644 index 0000000..0e5fe80 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/OverhaulStatusEnum.java @@ -0,0 +1,31 @@ +package com.vetti.common.enums.command; +/** + * 检修状态 + */ +public enum OverhaulStatusEnum { + + STATUS_0("0", "未检修"), + STATUS_1("1", "已检修"), + + ; + + private final String code; + private final String info; + + OverhaulStatusEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/StatusEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/StatusEnum.java new file mode 100644 index 0000000..623aaae --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/StatusEnum.java @@ -0,0 +1,32 @@ +package com.vetti.common.enums.command; + +/** + * 共通状态枚举 + */ +public enum StatusEnum { + + STATUS_0("0", "停用"), + STATUS_1("1", "启用"), + + ; + + private final String code; + private final String info; + + StatusEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/UserNowTypeEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/UserNowTypeEnum.java new file mode 100644 index 0000000..a96643a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/UserNowTypeEnum.java @@ -0,0 +1,31 @@ +package com.vetti.common.enums.command; +/** + * 用户当前类型 + */ +public enum UserNowTypeEnum { + + USER_NOW_TYPE_ONE("ONE", "司机未完善"), + USER_NOW_TYPE_TWO("TWO", "车辆未完善"), + USER_NOW_TYPE_THREE("THREE", "全部完善"), + ; + + private final String code; + private final String info; + + UserNowTypeEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/VehicleTypesEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/VehicleTypesEnum.java new file mode 100644 index 0000000..9fc5b32 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/VehicleTypesEnum.java @@ -0,0 +1,33 @@ +package com.vetti.common.enums.command; + +/** + * 车辆类型枚举 + */ +public enum VehicleTypesEnum { + PRIME_MOVERS("primeMovers", "Prime Movers"), + B_DOUBLES("bDoubles", "B-Doubles"), + ROAD_TRAINS("roadTrains", "Road Trains"), + RIGID_TRUCKS("rigidTrucks", "Rigid Trucks"), + + + ; + + private final String code; + private final String info; + + VehicleTypesEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/command/WorkStatusEnum.java b/vetti-common/src/main/java/com/vetti/common/enums/command/WorkStatusEnum.java new file mode 100644 index 0000000..d3e954e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/command/WorkStatusEnum.java @@ -0,0 +1,34 @@ +package com.vetti.common.enums.command; + +/** + * 工作状态枚举 + * + */ +public enum WorkStatusEnum { + + ACTIVE("active", "Active"), + MAINTENANCE("maintenance", "Maintenance"), + IDLE("idle", "Idle"), + LOADING("loading", "Loading"), + ; + + private final String code; + private final String info; + + WorkStatusEnum(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapAvoidFeatures.java b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapAvoidFeatures.java new file mode 100644 index 0000000..bfe9963 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapAvoidFeatures.java @@ -0,0 +1,29 @@ +package com.vetti.common.enums.hereMap; + +public enum HereMapAvoidFeatures { + + HIGHWAY("controlledAccessHighway", "高速公路"), + TOLLROAD("tollroad", "收费公路"), + FERRY("ferry", "渡轮"), + TUNNEL("tunnel", "隧道"), + DIFFICULT_TURNS("difficultTurns", "掉头"), + DIRT_ROAD("dirtRoad", "土路"); + + private String code; + private String name; + + + HereMapAvoidFeatures(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapHazmatType.java b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapHazmatType.java new file mode 100644 index 0000000..9db8d68 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapHazmatType.java @@ -0,0 +1,28 @@ +package com.vetti.common.enums.hereMap; + +public enum HereMapHazmatType { + + EXPLOSIVE("explosive","爆炸品"),GAS("gas","气体"),FLAMMABLE("flammable","易燃液体"), + COMBUSTIBLE("combustible","易燃固体"),ORGANIC("organic","有机过氧化物"),POISON("poison","毒性物质"), + RADIOACTIVE("radioactive","放射性物质"),CORROSIVE("corrosive","腐蚀性物质"), POISONOUS_INHALATION("poisonousInhalation","吸入有毒物质"), + HARMFUL_TO_WATER("harmfulToWater","对水环境有害"),OTHER("other","其他危险品"); + + + private String code; + private String name; + + + HereMapHazmatType(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapRoutingMode.java b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapRoutingMode.java new file mode 100644 index 0000000..4a3719f --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapRoutingMode.java @@ -0,0 +1,25 @@ +package com.vetti.common.enums.hereMap; + +public enum HereMapRoutingMode { + + FASTEST("fast", "最快路线"), SHORTEST("short", "最短路线"); + + + private String code; + private String name; + + + HereMapRoutingMode(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapTransportMode.java b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapTransportMode.java new file mode 100644 index 0000000..913e501 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/enums/hereMap/HereMapTransportMode.java @@ -0,0 +1,26 @@ +package com.vetti.common.enums.hereMap; + +public enum HereMapTransportMode { + + CAR("car","汽车"),TRUCK("truck","卡车"),PEDESTRIAN("pedestrian","行人"), + SCOOTER("scooter","踏板车"),BICYCLE("bicycle","自行车"),TAXI("taxi","出租车"), + BUS("bus","公交车"),PRIVATEBUS("privateBus","私人巴士"); + + private String code; + private String name; + + + HereMapTransportMode(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/DemoModeException.java b/vetti-common/src/main/java/com/vetti/common/exception/DemoModeException.java new file mode 100644 index 0000000..9123746 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.vetti.common.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/GlobalException.java b/vetti-common/src/main/java/com/vetti/common/exception/GlobalException.java new file mode 100644 index 0000000..9ec88e5 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.vetti.common.exception; + +/** + * 全局异常 + * + * @author ruoyi + */ +public class GlobalException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/exception/ServiceException.java b/vetti-common/src/main/java/com/vetti/common/exception/ServiceException.java new file mode 100644 index 0000000..ab99d4f --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/ServiceException.java @@ -0,0 +1,74 @@ +package com.vetti.common.exception; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/exception/UtilException.java b/vetti-common/src/main/java/com/vetti/common/exception/UtilException.java new file mode 100644 index 0000000..9140537 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.vetti.common.exception; + +/** + * 工具类异常 + * + * @author ruoyi + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/base/BaseException.java b/vetti-common/src/main/java/com/vetti/common/exception/base/BaseException.java new file mode 100644 index 0000000..0906d27 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.vetti.common.exception.base; + +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.StringUtils; + +/** + * 基础异常 + * + * @author ruoyi + */ +public class BaseException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() + { + String message = null; + if (!StringUtils.isEmpty(code)) + { + message = MessageUtils.message(code, args); + } + if (message == null) + { + message = defaultMessage; + } + return message; + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/file/FileException.java b/vetti-common/src/main/java/com/vetti/common/exception/file/FileException.java new file mode 100644 index 0000000..ab0d0da --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.vetti.common.exception.file; + +import com.vetti.common.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author ruoyi + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args, null); + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/file/FileNameLengthLimitExceededException.java b/vetti-common/src/main/java/com/vetti/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..55494f4 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author ruoyi + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/file/FileSizeLimitExceededException.java b/vetti-common/src/main/java/com/vetti/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..71ed6e2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author ruoyi + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/file/FileUploadException.java b/vetti-common/src/main/java/com/vetti/common/exception/file/FileUploadException.java new file mode 100644 index 0000000..fcba591 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/file/FileUploadException.java @@ -0,0 +1,61 @@ +package com.vetti.common.exception.file; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * 文件上传异常类 + * + * @author ruoyi + */ +public class FileUploadException extends Exception +{ + + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public FileUploadException() + { + this(null, null); + } + + public FileUploadException(final String msg) + { + this(msg, null); + } + + public FileUploadException(String msg, Throwable cause) + { + super(msg); + this.cause = cause; + } + + @Override + public void printStackTrace(PrintStream stream) + { + super.printStackTrace(stream); + if (cause != null) + { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + @Override + public void printStackTrace(PrintWriter writer) + { + super.printStackTrace(writer); + if (cause != null) + { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + @Override + public Throwable getCause() + { + return cause; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/file/InvalidExtensionException.java b/vetti-common/src/main/java/com/vetti/common/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..980d93d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,80 @@ +package com.vetti.common.exception.file; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author ruoyi + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/job/TaskException.java b/vetti-common/src/main/java/com/vetti/common/exception/job/TaskException.java new file mode 100644 index 0000000..2c72b9e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.vetti.common.exception.job; + +/** + * 计划策略异常 + * + * @author ruoyi + */ +public class TaskException extends Exception +{ + private static final long serialVersionUID = 1L; + + private Code code; + + public TaskException(String msg, Code code) + { + this(msg, code, null); + } + + public TaskException(String msg, Code code, Exception nestedEx) + { + super(msg, nestedEx); + this.code = code; + } + + public Code getCode() + { + return code; + } + + public enum Code + { + TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/BlackListException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/BlackListException.java new file mode 100644 index 0000000..017a6bd --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.user; + +/** + * 黑名单IP异常类 + * + * @author ruoyi + */ +public class BlackListException extends UserException +{ + private static final long serialVersionUID = 1L; + + public BlackListException() + { + super("login.blocked", null); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaException.java new file mode 100644 index 0000000..725cf43 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.user; + +/** + * 验证码错误异常类 + * + * @author ruoyi + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaExpireException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..b502b11 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author ruoyi + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/UserException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/UserException.java new file mode 100644 index 0000000..f36466a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.vetti.common.exception.user; + +import com.vetti.common.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author ruoyi + */ +public class UserException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) + { + super("user", code, args, null); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/UserNotExistsException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/UserNotExistsException.java new file mode 100644 index 0000000..cb23778 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/UserNotExistsException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.user; + +/** + * 用户不存在异常类 + * + * @author ruoyi + */ +public class UserNotExistsException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserNotExistsException() + { + super("user.not.exists", null); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordNotMatchException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..8e15475 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author ruoyi + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordRetryLimitExceedException.java b/vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..366d601 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.vetti.common.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/filter/PropertyPreExcludeFilter.java b/vetti-common/src/main/java/com/vetti/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 0000000..df2e31a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.vetti.common.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author ruoyi + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/filter/RepeatableFilter.java b/vetti-common/src/main/java/com/vetti/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..d7659f5 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.vetti.common.filter; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.vetti.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/filter/RepeatedlyRequestWrapper.java b/vetti-common/src/main/java/com/vetti/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..5a98406 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.vetti.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.vetti.common.utils.http.HttpHelper; +import com.vetti.common.constant.Constants; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/filter/XssFilter.java b/vetti-common/src/main/java/com/vetti/common/filter/XssFilter.java new file mode 100644 index 0000000..0930098 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/filter/XssFilter.java @@ -0,0 +1,75 @@ +package com.vetti.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.enums.HttpMethod; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] urls = tempExcludes.split(","); + for (String url : urls) + { + excludes.add(url); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/filter/XssHttpServletRequestWrapper.java b/vetti-common/src/main/java/com/vetti/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..7dba451 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.vetti.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/handle/JsonTypeHandler.java b/vetti-common/src/main/java/com/vetti/common/handle/JsonTypeHandler.java new file mode 100644 index 0000000..16f2024 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/handle/JsonTypeHandler.java @@ -0,0 +1,60 @@ +package com.vetti.common.handle; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author ID + * @date 2025/9/15 9:32 + */ +public class JsonTypeHandler extends BaseTypeHandler { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private final Class type; + + public JsonTypeHandler(Class type) { + this.type = type; + } + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { + try { + ps.setString(i, objectMapper.writeValueAsString(parameter)); + } catch (JsonProcessingException e) { + throw new SQLException("Failed to convert object to JSON", e); + } + } + + @Override + public T getNullableResult(ResultSet rs, String columnName) throws SQLException { + String json = rs.getString(columnName); + return parseJson(json); + } + + @Override + public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String json = rs.getString(columnIndex); + return parseJson(json); + } + + @Override + public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String json = cs.getString(columnIndex); + return parseJson(json); + } + + private T parseJson(String json) throws SQLException { + if (json == null) return null; + try { + return objectMapper.readValue(json, type); + } catch (JsonProcessingException e) { + throw new SQLException("Failed to parse JSON", e); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/international/International.java b/vetti-common/src/main/java/com/vetti/common/international/International.java new file mode 100644 index 0000000..706e5df --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/international/International.java @@ -0,0 +1,26 @@ +package com.vetti.common.international; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段国际化 + * + * @author wangxiangshun + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@JacksonAnnotationsInside +@JsonSerialize(using = InternationalSerializer.class) +public @interface International { + + /** + * 所属类型 + */ + String type() default ""; +} diff --git a/vetti-common/src/main/java/com/vetti/common/international/InternationalResourceGetter.java b/vetti-common/src/main/java/com/vetti/common/international/InternationalResourceGetter.java new file mode 100644 index 0000000..d6677ea --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/international/InternationalResourceGetter.java @@ -0,0 +1,20 @@ +package com.vetti.common.international; + +/** + * 国际资源获取器 + * + * @author wangxiangshun + */ +public interface InternationalResourceGetter { + + /** + * 获取文本 + * + * @param resourceId + * 资源ID + * @param lang + * 语言 + * @return 国际化文本 + */ + String getText(String resourceId, String lang); +} diff --git a/vetti-common/src/main/java/com/vetti/common/international/InternationalResourceSetter.java b/vetti-common/src/main/java/com/vetti/common/international/InternationalResourceSetter.java new file mode 100644 index 0000000..47d0034 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/international/InternationalResourceSetter.java @@ -0,0 +1,30 @@ +package com.vetti.common.international; + +/** + * 国际资源设置器 + * + * @author wangxiangshun + */ +public interface InternationalResourceSetter { + + /** + * 创建资源 + * + * @param text + * 文本 + * @param type + * 类型 + * @param lang + * 语言 + * @return 资源ID + */ + String create(String text, String type, String lang); + + /** + * 删除资源 + * + * @param resourceId + * 资源ID + */ + void remove(String resourceId); +} diff --git a/vetti-common/src/main/java/com/vetti/common/international/InternationalSerializer.java b/vetti-common/src/main/java/com/vetti/common/international/InternationalSerializer.java new file mode 100644 index 0000000..51a9449 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/international/InternationalSerializer.java @@ -0,0 +1,46 @@ +package com.vetti.common.international; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.vetti.common.config.InternationalProperties; +import com.vetti.common.utils.spring.SpringUtils; + +import java.io.IOException; + +/** + * 国际化序列化器 + * + * @author wangxiangshun + */ +public class InternationalSerializer extends JsonSerializer { + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (null == value) { + gen.writeNull(); + return; + } + + InternationalResourceGetter iTextGetter = SpringUtils.getBean(InternationalResourceGetter.class); + InternationalProperties iProperties = SpringUtils.getBean(InternationalProperties.class); + if (null == iTextGetter || !iProperties.isEnable()) { + gen.writeString(value); + return; + } + + // 获取客户语言环境 + String lang = iProperties.getLang(); + if (!StrUtil.isBlank(lang)) { + String itext = iTextGetter.getText(value, lang); + if (null == itext) { + gen.writeNull(); + } else { + gen.writeString(itext); + } + } else { + gen.writeString(value); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/security/service/AppTokenService.java b/vetti-common/src/main/java/com/vetti/common/security/service/AppTokenService.java new file mode 100644 index 0000000..6e77c70 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/security/service/AppTokenService.java @@ -0,0 +1,175 @@ +package com.vetti.common.security.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; + +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.redis.RedisService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.SecurityConstants; +import com.vetti.common.utils.JwtUtils; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.common.utils.uuid.IdUtils; +import com.vetti.common.utils.SecurityUtils; + +/** + * token验证处理 + * + * @author hamkke + */ +@Component +public class AppTokenService +{ + private static final Logger log = LoggerFactory.getLogger(AppTokenService.class); + + @Autowired + private RedisService redisService; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private final static long expireTime = CacheConstants.EXPIRATION; + + private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; + + private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; + + /** + * 创建令牌 + */ + public Map createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + Long userId = loginUser.getUser().getUserId(); + String userName = loginUser.getUser().getUserName(); + loginUser.setToken(token); + loginUser.setUserId(userId); + loginUser.setUsername(userName); + loginUser.setIpaddr(IpUtils.getIpAddr()); + refreshToken(loginUser); + + // Jwt存储信息 + Map claimsMap = new HashMap(); + claimsMap.put(SecurityConstants.USER_KEY, token); + claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); + claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); + + // 接口返回信息 + Map rspMap = new HashMap(); + rspMap.put("access_token", JwtUtils.createToken(claimsMap)); + rspMap.put("expires_in", expireTime); + return rspMap; + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser() + { + return getLoginUser(ServletUtils.getRequest()); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = SecurityUtils.getToken(request); + return getLoginUser(token); + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(String token) + { + LoginUser user = null; + try + { + if (StringUtils.isNotEmpty(token)) + { + String userkey = JwtUtils.getUserKey(token); + user = redisService.getCacheObject(getTokenKey(userkey)); + return user; + } + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + return user; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户缓存信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userkey = JwtUtils.getUserKey(token); + redisService.deleteObject(getTokenKey(userkey)); + } + } + + /** + * 验证令牌有效期,相差不足120分钟,自动刷新缓存 + * + * @param loginUser + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + private String getTokenKey(String token) + { + return ACCESS_TOKEN + token; + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapGeocoderService.java b/vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapGeocoderService.java new file mode 100644 index 0000000..42bbbec --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapGeocoderService.java @@ -0,0 +1,12 @@ +package com.vetti.common.service.hereMap; + +import com.vetti.common.entity.hereMap.HereMapLocationDto; + +import java.util.List; + +public interface HereMapGeocoderService { + + List getCoordinates(String location); + + HereMapLocationDto getLocationFromCoordinates(double latitude, double longitude); +} diff --git a/vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapRoutingService.java b/vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapRoutingService.java new file mode 100644 index 0000000..1f48e53 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/hereMap/HereMapRoutingService.java @@ -0,0 +1,10 @@ +package com.vetti.common.service.hereMap; + +import com.vetti.common.entity.hereMap.HereMapRouteVo; +import com.vetti.common.entity.hereMap.route.HereMapRouteDto; + +public interface HereMapRoutingService { + + HereMapRouteDto findRouting(HereMapRouteVo truckRoute); + +} diff --git a/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/BaseHereMapsService.java b/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/BaseHereMapsService.java new file mode 100644 index 0000000..7627bb9 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/BaseHereMapsService.java @@ -0,0 +1,103 @@ +package com.vetti.common.service.hereMap.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vetti.common.config.HereMapsProperties; +import com.vetti.common.entity.hereMap.HereMapCoordinates; +import com.vetti.common.entity.hereMap.queryParam.HereMapQueryParam; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.http.client.utils.URIBuilder; +import org.springframework.beans.factory.annotation.Value; + +import javax.annotation.Resource; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.List; + +/** + * @author ID + * @date 2025/9/4 21:42 + */ +@Log4j2 +public abstract class BaseHereMapsService { + + @Resource + protected HereMapsProperties hereMapsProperties; + + @Resource + protected HttpClient httpClient; + + @Resource + protected ObjectMapper objectMapper; + + @Value("${http.client.connect-timeout-seconds:10}") + protected Integer connectTimeoutSeconds; + + + /** + * 构建HTTP请求 + */ + protected HttpRequest buildHttpRequest(URI uri) { + return HttpRequest.newBuilder() + .uri(uri) + .timeout(Duration.ofSeconds(connectTimeoutSeconds)) + .header("Accept", "application/json") + .GET() + .build(); + } + + protected URI buildUri(String url, List queryParams) throws Exception { + String uriStr = url + "?" + hereMapQueryParamToStr(queryParams) + apiKey(); + return new URIBuilder(uriStr).build(); + } + + /** + * 发送HTTP请求并处理响应状态 + */ + protected HttpResponse sendHttpRequest(HttpRequest request) throws Exception { + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + int statusCode = response.statusCode(); + if (statusCode != 200) { + String errorMsg = String.format( + "HERE Maps API请求失败:状态码=%d, 响应体=%s", + statusCode, response.body() + ); + log.error(errorMsg); + throw new Exception(errorMsg); + } + return response; + } + + /** + * 安全获取JSON节点文本,避免空指针 + */ + protected String getOptionalNodeText(JsonNode parentNode, String fieldName) { + JsonNode node = parentNode.get(fieldName); + return (node != null && !node.isNull()) ? node.asText() : ""; + } + + + private String apiKey() { + return "apiKey=" + hereMapsProperties.getApiKey(); + } + + private String hereMapQueryParamToStr(List queryParams) { + if (CollectionUtils.isNotEmpty(queryParams)) { + StringBuffer sb = new StringBuffer(); + queryParams.forEach(e -> { + if (e.getValue() instanceof HereMapCoordinates) { + sb.append(e.getKey()).append("=").append(((HereMapCoordinates) e.getValue()).getLatlng()).append("&"); + } else { + sb.append(e.getKey()).append("=").append(e.getValue()).append("&"); + } + }); + return sb.toString(); + } + return ""; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapGeocoderServiceImpl.java b/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapGeocoderServiceImpl.java new file mode 100644 index 0000000..134f5db --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapGeocoderServiceImpl.java @@ -0,0 +1,156 @@ +package com.vetti.common.service.hereMap.impl; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.vetti.common.entity.hereMap.HereMapLocationDto; +import com.vetti.common.entity.hereMap.queryParam.HereMapQueryParam; +import com.vetti.common.exception.UtilException; +import com.vetti.common.service.hereMap.HereMapGeocoderService; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * @author ID + * @date 2025/9/4 21:43 + */ +@Log4j2 +@Service +public class HereMapGeocoderServiceImpl extends BaseHereMapsService implements HereMapGeocoderService { + + /** + * 根据地点名称查询坐标 + * + * @param location 地点名称(如"北京天安门") + * @return 地点信息+坐标列表 + * @throws Exception 包含网络异常、API错误等 + */ + @Override + public List getCoordinates(String location) { + try { + // 1. 参数校验 + if (location == null || location.trim().isEmpty()) { + log.error("地点名称不能为空"); + throw new IllegalArgumentException("地点名称不能为空"); + } + // 2. 构建请求URL + List queryParams = new ArrayList<>(); + String encodedLocation = URLEncoder.encode(location, StandardCharsets.UTF_8.name()); + queryParams.add(HereMapQueryParam.build("q", encodedLocation)); + // 3. 构建并发送HTTP请求 + URI requestUri = buildUri(hereMapsProperties.getGeocodingApiUrl(), queryParams); + HttpRequest request = buildHttpRequest(requestUri); + HttpResponse response = sendHttpRequest(request); + // 4. 处理响应并返回结果 + log.info("HERE Maps API请求成功,响应体长度:{} 字符", response.body().length()); + return parseGeocodingResponse(response.body()); + } catch (Exception e) { + log.error("根据地点名称查询坐标信息失败", e); + throw new UtilException("根据地点名称查询坐标信息失败"); + } + + } + + /** + * 根据经纬度获取地理位置信息 + * + * @param latitude 纬度(-90到90之间) + * @param longitude 经度(-180到180之间) + * @return 地理位置信息Optional + */ + @Override + public HereMapLocationDto getLocationFromCoordinates(double latitude, double longitude) { + try { + // 1. 参数校验 + validateCoordinates(latitude, longitude); + List queryParams = new ArrayList<>(); + queryParams.add(HereMapQueryParam.build("at", latitude + "," + longitude)); + URI requestUri = buildUri(hereMapsProperties.getReverseGeocodingApiUrl(), queryParams); + HttpRequest request = buildHttpRequest(requestUri); + HttpResponse response = sendHttpRequest(request); + // 4. 处理响应并返回结果 + log.info("HERE Maps API请求成功,响应体长度:{} 字符", response.body().length()); + return parseLocationResponse(response.body(), latitude, longitude); + } catch (Exception e) { + log.error("获取地理位置信息失败", e); + throw new UtilException("获取地理位置信息失败"); + } + } + + /** + * 解析地理编码响应 + */ + private List parseGeocodingResponse(String responseBody) { + List results = new ArrayList<>(); + try { + JsonNode rootNode = objectMapper.readTree(responseBody); + JsonNode itemsNode = rootNode.get("items"); + if (itemsNode == null || !itemsNode.isArray() || itemsNode.size() == 0) { + log.warn("地理编码无匹配结果"); + return results; + } + for (JsonNode item : itemsNode) { + String title = item.get("title").asText(); + String address = item.get("address").get("label").asText(); + JsonNode position = item.get("position"); + double latitude = position.get("lat").asDouble(); + double longitude = position.get("lng").asDouble(); + results.add(new HereMapLocationDto(title, address, latitude, longitude)); + } + } catch (Exception e) { + log.error("解析地理编码响应失败", e); + throw new RuntimeException("解析地理编码结果失败", e); + } + return results; + } + + /** + * 解析逆地理编码响应 + */ + private HereMapLocationDto parseLocationResponse(String responseBody, double latitude, double longitude) { + try { + JsonNode rootNode = objectMapper.readTree(responseBody); + JsonNode itemsNode = rootNode.get("items"); + if (itemsNode != null && itemsNode.isArray() && itemsNode.size() > 0) { + JsonNode firstItem = itemsNode.get(0); + JsonNode addressNode = firstItem.get("address"); + HereMapLocationDto location = new HereMapLocationDto(); + location.setTitle(firstItem.get("title").asText()); + location.setLatitude(latitude); + location.setLongitude(longitude); + location.setAddress(addressNode.get("label").asText()); + location.setCity(getOptionalNodeText(addressNode, "city")); + location.setCountry(getOptionalNodeText(addressNode, "countryName")); + location.setPostalCode(getOptionalNodeText(addressNode, "postalCode")); + return location; + } + log.warn("逆地理编码无匹配结果"); + return null; + } catch (Exception e) { + log.error("解析逆地理编码响应失败", e); + throw new RuntimeException("解析地理位置结果失败", e); + } + } + + /** + * 校验经纬度合法性 + */ + private void validateCoordinates(double latitude, double longitude) { + if (latitude < -90 || latitude > 90) { + log.error("纬度值不合法: {}", latitude); + throw new IllegalArgumentException("纬度必须在-90到90之间"); + } + if (longitude < -180 || longitude > 180) { + log.error("经度值不合法: {}", longitude); + throw new IllegalArgumentException("经度必须在-180到180之间"); + } + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapRoutingServiceImpl.java b/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapRoutingServiceImpl.java new file mode 100644 index 0000000..08210d7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/hereMap/impl/HereMapRoutingServiceImpl.java @@ -0,0 +1,175 @@ +package com.vetti.common.service.hereMap.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.vetti.common.entity.hereMap.HereMapCoordinates; +import com.vetti.common.entity.hereMap.HereMapRouteVo; +import com.vetti.common.entity.hereMap.queryParam.HereMapQueryParam; +import com.vetti.common.entity.hereMap.queryParam.HereMapQueryParamTruck; +import com.vetti.common.entity.hereMap.route.HereMapRouteDto; +import com.vetti.common.entity.hereMap.vehicle.HereMapVehicleTruck; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.exception.UtilException; +import com.vetti.common.service.hereMap.HereMapRoutingService; +import com.vetti.common.utils.StringUtils; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +/** + * @author ID + * @date 2025/9/4 21:43 + */ +@Log4j2 +@Service +public class HereMapRoutingServiceImpl extends BaseHereMapsService implements HereMapRoutingService { + + @Override + public HereMapRouteDto findRouting(HereMapRouteVo truckRoute) { + try { + URI requestUri = buildRoutingUri(truckRoute); + HttpRequest request = buildHttpRequest(requestUri); + HttpResponse response = sendHttpRequest(request); + // 4. 处理响应并返回结果 + log.info("HERE Maps API请求成功,响应体长度:{} 字符", response.body().length()); + return parseRoutingResponse(response.body()); + } catch (Exception e) { + log.error("获取地理位置信息失败", e); + log.error("获取地理位置信息失败", e); + throw new UtilException("获取地理位置信息失败"); + } + } + + private HereMapRouteDto parseRoutingResponse(String body) { + if (StringUtils.isEmpty(body)) { + return null; + } + return JSONObject.parseObject(body, HereMapRouteDto.class); + } + + /** + * 构建路线规划请求URI + */ + private URI buildRoutingUri(HereMapRouteVo truckRoute) throws Exception { + List queryParams = new ArrayList<>(); + // 添加基本路线参数 + addBasicRoutingParameters(queryParams, truckRoute); + // 添加车辆参数 + addTruckParameters(queryParams, truckRoute); + + return buildUri(hereMapsProperties.getRouterApiUrl(), queryParams); + } + + private String queryParamsListToStr(List list) { + StringBuffer sb = new StringBuffer(); + for (String str : list) { + sb.append(str).append(","); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + /** + * 添加基本路线参数 + */ + private void addBasicRoutingParameters(List queryParams, HereMapRouteVo truckRoute) { + queryParams.add(HereMapQueryParam.build("origin", truckRoute.getOrigin())); + queryParams.add(HereMapQueryParam.build("destination", truckRoute.getDestination())); + if (CollectionUtils.isNotEmpty(truckRoute.getVia())) { + for (HereMapCoordinates via : truckRoute.getVia()) { + queryParams.add(HereMapQueryParam.build("via", via.getLatlng())); + } + } + if (CollectionUtils.isNotEmpty(truckRoute.getVia())) { + queryParams.add(HereMapQueryParam.build("avoid[features]", queryParamsListToStr(truckRoute.getAvoid()))); + } + + queryParams.add(HereMapQueryParam.build("transportMode", truckRoute.getTransportMode())); + + queryParams.add(HereMapQueryParam.build("routingMode", truckRoute.getRoutingMode())); + + if (StringUtils.isEmpty(truckRoute.getReturnStr())) { + queryParams.add(HereMapQueryParam.build("return", + "polyline,actions,instructions,summary,travelSummary,typicalDuration,turnByTurnActions,elevation,routeHandle,passthrough,incidents,routingZones,truckRoadTypes,tolls,routeLabels,potentialTimeDependentViolations,noThroughRestrictions" + )); + } else { + queryParams.add(HereMapQueryParam.build("return", truckRoute.getReturnStr())); + } + if (StringUtils.isEmpty(truckRoute.getLang())) { + queryParams.add(HereMapQueryParam.build("lang", "zh-CN")); + } else { + queryParams.add(HereMapQueryParam.build("lang", truckRoute.getLang())); + } + if (StringUtils.isEmpty(truckRoute.getUnits())) { + queryParams.add(HereMapQueryParam.build("units", "metric")); + } else { + queryParams.add(HereMapQueryParam.build("units", truckRoute.getUnits())); + } + if (StringUtils.isNotEmpty(truckRoute.getDepartureTime())) { + queryParams.add(HereMapQueryParam.build("departureTime", truckRoute.getDepartureTime())); + } + if (StringUtils.isNotEmpty(truckRoute.getArrivalTime())) { + queryParams.add(HereMapQueryParam.build("arrivalTime", truckRoute.getArrivalTime())); + } + + + } + + /** + * 添加车辆参数 + */ + private void addTruckParameters(List queryParams, HereMapRouteVo truckRoute) { + HereMapQueryParamTruck hereMapQueryParamTruck = new HereMapQueryParamTruck(); + HereMapVehicleTruck vehicle = truckRoute.getVehicle(); + if (vehicle == null) { + return; + } + if (vehicle.getHeight() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[height]", vehicle.getHeight())); + } + if (vehicle.getWidth() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[width]", vehicle.getWidth())); + } + if (vehicle.getLength() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[length]", vehicle.getLength())); + } + if (vehicle.getSpeedCap() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[speedCap]", vehicle.getSpeedCap())); + } + if (vehicle.getGrossWeight() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[grossWeight]", vehicle.getGrossWeight())); + } + if (vehicle.getCurrentWeight() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[currentWeight]", vehicle.getCurrentWeight())); + } + + if (vehicle.getAxleCount() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[axleCount]", vehicle.getAxleCount())); + } + if (vehicle.getTrailerAxleCount() != null) { + if (vehicle.getAxleCount() >= 2 && (vehicle.getTrailerAxleCount() >= 1 && vehicle.getTrailerAxleCount() <= (vehicle.getAxleCount() - 1))) { + queryParams.add(HereMapQueryParam.build("vehicle[trailerAxleCount]", vehicle.getTrailerAxleCount())); + } else { + throw new ServiceException("区间trailerAxleCount:[1,(axleCount-1)]"); + } + } + if (vehicle.getWeightPerAxle() != null) { + queryParams.add(HereMapQueryParam.build("vehicle[weightPerAxle]", vehicle.getWeightPerAxle())); + } + if (vehicle.getTrailerCount() != null && (vehicle.getTrailerAxleCount() >= vehicle.getTrailerCount())) { + queryParams.add(HereMapQueryParam.build("vehicle[trailerCount]", vehicle.getTrailerCount())); + } + if (!StringUtils.isEmpty(vehicle.getType())) { + queryParams.add(HereMapQueryParam.build("vehicle[type]", vehicle.getType())); + } + if (!StringUtils.isEmpty(vehicle.getCargoType()) && "hazardous".equals(vehicle.getCargoType()) && CollectionUtils.isNotEmpty(vehicle.getHazardousGoods())) { + queryParams.add(HereMapQueryParam.build("vehicle[shippedHazardousGoods]", String.join(",", vehicle.getHazardousGoods()))); + } + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/service/verification/VerificationEmailService.java b/vetti-common/src/main/java/com/vetti/common/service/verification/VerificationEmailService.java new file mode 100644 index 0000000..5eb2836 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/verification/VerificationEmailService.java @@ -0,0 +1,42 @@ +package com.vetti.common.service.verification; + +import com.vetti.common.entity.verification.BaseTemplateEmail; + +public interface VerificationEmailService { + + /** + * 发送邮箱验证码(内容走的配置文件) + * + * @param email 收件人邮箱 + * @return + */ + boolean sendVerificationCode(String email); + + /** + * 发动邮箱验证码 (内容走的官网 配置好的模板) + * + * @param email 收件人邮箱 + * @return + */ + boolean sendVerificationRoutezVerificationCode(String email); + + /** + * 发动邮箱验证码 (内容走的官网配置模板) + * + * @param email 收件人邮箱 + * @param code 验证码 + * @param templateId 官网模板ID + * @param templateEmail 模板参数实体 + * @return + */ + boolean sendTemplateCode(String email,String code, String templateId, BaseTemplateEmail templateEmail); + + /** + * 验证验证码是否有效 + * + * @param email 收件人邮箱 + * @param code + * @return + */ + boolean verifyCode(String email, String code); +} diff --git a/vetti-common/src/main/java/com/vetti/common/service/verification/impl/VerificationEmailServiceImpl.java b/vetti-common/src/main/java/com/vetti/common/service/verification/impl/VerificationEmailServiceImpl.java new file mode 100644 index 0000000..eae0274 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/service/verification/impl/VerificationEmailServiceImpl.java @@ -0,0 +1,143 @@ +package com.vetti.common.service.verification.impl; + +import com.vetti.common.config.TwilioConfig; +import com.vetti.common.config.VerificationEmailConfig; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.entity.verification.BaseTemplateEmail; +import com.vetti.common.entity.verification.RoutezVerificationCodeTemplate; +import com.vetti.common.service.verification.VerificationEmailService; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.email.EmailUtil; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.text.MessageFormat; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * @author ID + * @date 2025/9/4 16:49 + */ +@Service +public class VerificationEmailServiceImpl implements VerificationEmailService { + + @Resource + private TwilioConfig twilioConfig; + + @Resource + private VerificationEmailConfig verificationConfig; + + @Resource + private EmailUtil emailUtil; + + @Resource + private RedisCache redisCache; + + + /** + * 发送邮箱验证码(内容走的配置文件) + * + * @param email 收件人邮箱 + * @return + */ + @Override + public boolean sendVerificationCode(String email) { + String code = generateVerificationCode(); + return sendVerificationCode(email, MessageUtils.messageCustomize("VerificationEmailTiTle"), + MessageUtils.messageCustomize("VerificationEmailContent"), code); + + } + + /** + * 发动邮箱验证码 (内容走的官网 配置好的模板) + * + * @param email 收件人邮箱 + * @return + */ + @Override + public boolean sendVerificationRoutezVerificationCode(String email) { + String code = generateVerificationCode(); + RoutezVerificationCodeTemplate template = new RoutezVerificationCodeTemplate(); + template.setVerification_code(code); + template.setVerification_expiration(verificationConfig.getExpirationMinutes()); + return sendTemplateCode(email,code, + twilioConfig.getTemplateIds().getRoutezVerificationCode(), template); + } + + /** + * 发动邮箱验证码 (内容走的官网配置模板) + * + * @param email 收件人邮箱 + * @param code 验证码 + * @param templateId 官网模板ID + * @param templateEmail 模板参数实体 + * @return + */ + @Override + public boolean sendTemplateCode(String email,String code, String templateId, BaseTemplateEmail templateEmail) { + try { + redisCache.setCacheObject(CacheConstants.VERIFICATION_EMAIL_CODE_KEY + email, code, + verificationConfig.getExpirationMinutes(), TimeUnit.MINUTES); + emailUtil.sendEmail(email, templateId, templateEmail); + return true; + } catch (Exception e) { + // 记录日志 + e.printStackTrace(); + return false; + } + } + + /** + * 验证验证码是否有效 + * + * @param email 收件人邮箱 + * @param code + * @return + */ + @Override + public boolean verifyCode(String email, String code) { + String codeR = redisCache.getCacheObject(CacheConstants.VERIFICATION_EMAIL_CODE_KEY + email); + if (codeR == null) {//过期 + return false; + } + if (!code.equals(codeR)) { + return false; + } + return true; + } + + + private boolean sendVerificationCode(String email, String subject, String content) { + String code = generateVerificationCode(); + return sendVerificationCode(email, subject, content, code); + } + + /** + * 生成随机验证码 + */ + private String generateVerificationCode() { + int length = verificationConfig.getLength(); + StringBuilder codeBuilder = new StringBuilder(); + Random random = new Random(); + for (int i = 0; i < length; i++) { + codeBuilder.append(random.nextInt(10)); + } + return codeBuilder.toString(); + } + + private boolean sendVerificationCode(String email, String subject, String content, String code) { + try { + redisCache.setCacheObject(CacheConstants.VERIFICATION_EMAIL_CODE_KEY + email, code, + verificationConfig.getExpirationMinutes(), TimeUnit.MINUTES); + emailUtil.sendEmail(email, subject, MessageFormat.format(content, code, verificationConfig.getExpirationMinutes())); + return true; + } catch (Exception e) { + // 记录日志 + e.printStackTrace(); + return false; + } + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/Arith.java b/vetti-common/src/main/java/com/vetti/common/utils/Arith.java new file mode 100644 index 0000000..92e86d2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/Arith.java @@ -0,0 +1,113 @@ +package com.vetti.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author ruoyi + */ +public class Arith +{ + + /** 默认除法运算精度 */ + private static final int DEF_DIV_SCALE = 10; + + /** 这个类不能实例化 */ + private Arith() + { + } + + /** + * 提供精确的加法运算。 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) + { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) + { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + return b.divide(BigDecimal.ONE, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/DateUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/DateUtils.java new file mode 100644 index 0000000..8a009c3 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/DateUtils.java @@ -0,0 +1,220 @@ +package com.vetti.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @author ruoyi + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils { + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDD = "yyyyMMdd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) { + try { + return new SimpleDateFormat(format).parse(ts); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) { + if (str == null) { + return null; + } + try { + return parseDate(str.toString(), parsePatterns); + } catch (ParseException e) { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算时间差 + * + * @param endDate 最后时间 + * @param startTime 开始时间 + * @return 时间差(天/小时/分钟) + */ + public static String timeDistance(Date endDate, Date startTime) { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - startTime.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 计算两个Date类型时间的差值 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param unit 时间单位:"day"、"hour"、"minute"、"second"、"millisecond" + * @return 两个时间的差值,返回对应单位的数值 + */ + public static long calculateDifference(Date startTime, Date endTime, String unit) { + // 转换为毫秒数 + long startMillis = startTime.getTime(); + long endMillis = endTime.getTime(); + + // 计算差值(取绝对值,避免负数) + long diffMillis = Math.abs(endMillis - startMillis); + + // 根据单位返回不同结果 + switch (unit.toLowerCase()) { + case "day": + return diffMillis / (1000 * 60 * 60 * 24); + case "hour": + return diffMillis / (1000 * 60 * 60); + case "minute": + return diffMillis / (1000 * 60); + case "second": + return diffMillis / 1000; + case "millisecond": + return diffMillis; + default: + throw new IllegalArgumentException("不支持的时间单位:" + unit + + ",请使用 day、hour、minute、second 或 millisecond"); + } + } + + /** + * 计算两个LocalDateTime类型时间的差值 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param unit 时间单位:"day"、"hour"、"minute"、"second"、"millisecond" + * @return 两个时间的差值 + */ + public static long calculateDifference(LocalDateTime startTime, LocalDateTime endTime, String unit) { + // 转换为Date类型再计算 + Date startDate = Date.from(startTime.atZone(ZoneId.systemDefault()).toInstant()); + Date endDate = Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()); + return calculateDifference(startDate, endDate, unit); + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/DesensitizedUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/DesensitizedUtil.java new file mode 100644 index 0000000..31244fc --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/DesensitizedUtil.java @@ -0,0 +1,49 @@ +package com.vetti.common.utils; + +/** + * 脱敏工具类 + * + * @author ruoyi + */ +public class DesensitizedUtil +{ + /** + * 密码的全部字符都用*代替,比如:****** + * + * @param password 密码 + * @return 脱敏后的密码 + */ + public static String password(String password) + { + if (StringUtils.isBlank(password)) + { + return StringUtils.EMPTY; + } + return StringUtils.repeat('*', password.length()); + } + + /** + * 车牌中间用*代替,如果是错误的车牌,不处理 + * + * @param carLicense 完整的车牌号 + * @return 脱敏后的车牌 + */ + public static String carLicense(String carLicense) + { + if (StringUtils.isBlank(carLicense)) + { + return StringUtils.EMPTY; + } + // 普通车牌 + if (carLicense.length() == 7) + { + carLicense = StringUtils.hide(carLicense, 3, 6); + } + else if (carLicense.length() == 8) + { + // 新能源车牌 + carLicense = StringUtils.hide(carLicense, 3, 7); + } + return carLicense; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/DictUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/DictUtils.java new file mode 100644 index 0000000..d3f26f7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/DictUtils.java @@ -0,0 +1,239 @@ +package com.vetti.common.utils; + +import java.util.Collection; +import java.util.List; +import com.alibaba.fastjson2.JSONArray; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.domain.entity.SysDictData; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.utils.spring.SpringUtils; + +/** + * 字典工具类 + * + * @author ruoyi + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List getDictCache(String key) + { + JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) + { + return arrayCache.toList(SysDictData.class); + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + if (StringUtils.isEmpty(dictValue)) + { + return StringUtils.EMPTY; + } + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + if (StringUtils.isEmpty(dictLabel)) + { + return StringUtils.EMPTY; + } + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + if (StringUtils.containsAny(separator, dictValue)) + { + for (SysDictData dict : datas) + { + for (String value : dictValue.split(separator)) + { + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictValue.equals(dict.getDictValue())) + { + return dict.getDictLabel(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + if (StringUtils.containsAny(separator, dictLabel)) + { + for (SysDictData dict : datas) + { + for (String label : dictLabel.split(separator)) + { + if (label.equals(dict.getDictLabel())) + { + propertyString.append(dict.getDictValue()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictLabel.equals(dict.getDictLabel())) + { + return dict.getDictValue(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 根据字典类型获取字典所有值 + * + * @param dictType 字典类型 + * @return 字典值 + */ + public static String getDictValues(String dictType) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + for (SysDictData dict : datas) + { + propertyString.append(dict.getDictValue()).append(SEPARATOR); + } + return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + + /** + * 根据字典类型获取字典所有标签 + * + * @param dictType 字典类型 + * @return 字典值 + */ + public static String getDictLabels(String dictType) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + for (SysDictData dict : datas) + { + propertyString.append(dict.getDictLabel()).append(SEPARATOR); + } + return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache(String key) + { + SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + Collection keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String configKey) + { + return CacheConstants.SYS_DICT_KEY + configKey; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/ExceptionUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/ExceptionUtil.java new file mode 100644 index 0000000..ad82f52 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.vetti.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author ruoyi + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/I18nUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/I18nUtil.java new file mode 100644 index 0000000..1f58801 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/I18nUtil.java @@ -0,0 +1,41 @@ +package com.vetti.common.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertiesLoaderUtils; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Properties; + +/** + * i18n 国际化工具类 + * + * @author Wangxiangshun 2025-08-27 + */ +public class I18nUtil { + + private static Logger logger = LoggerFactory.getLogger(I18nUtil.class); + + /** + * 获取国际化信息 + * @param languageType + * @return + */ + public static Properties loadI18nProp(String languageType) { + Properties prop = null; + try { + String i18n = languageType; + String i18nFile = MessageFormat.format("i18n/messages_{0}.properties", i18n); + Resource resource = new ClassPathResource(i18nFile); + EncodedResource encodedResource = new EncodedResource(resource, "UTF-8"); + prop = PropertiesLoaderUtils.loadProperties(encodedResource); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return prop; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/JwtUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/JwtUtils.java new file mode 100644 index 0000000..84774c6 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/JwtUtils.java @@ -0,0 +1,124 @@ +package com.vetti.common.utils; + +import java.util.Map; + +import com.vetti.common.constant.SecurityConstants; +import com.vetti.common.constant.TokenConstants; +import com.vetti.common.core.text.Convert; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * Jwt工具类 + * + * @author wangxiangshun + */ +public class JwtUtils +{ + public static String secret = TokenConstants.SECRET; + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + public static String createToken(Map claims) + { + String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + public static Claims parseToken(String token) + { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } + + /** + * 根据令牌获取用户标识 + * + * @param token 令牌 + * @return 用户ID + */ + public static String getUserKey(String token) + { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USER_KEY); + } + + /** + * 根据令牌获取用户标识 + * + * @param claims 身份信息 + * @return 用户ID + */ + public static String getUserKey(Claims claims) + { + return getValue(claims, SecurityConstants.USER_KEY); + } + + /** + * 根据令牌获取用户ID + * + * @param token 令牌 + * @return 用户ID + */ + public static String getUserId(String token) + { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.DETAILS_USER_ID); + } + + /** + * 根据身份信息获取用户ID + * + * @param claims 身份信息 + * @return 用户ID + */ + public static String getUserId(Claims claims) + { + return getValue(claims, SecurityConstants.DETAILS_USER_ID); + } + + /** + * 根据令牌获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public static String getUserName(String token) + { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.DETAILS_USERNAME); + } + + /** + * 根据身份信息获取用户名 + * + * @param claims 身份信息 + * @return 用户名 + */ + public static String getUserName(Claims claims) + { + return getValue(claims, SecurityConstants.DETAILS_USERNAME); + } + + /** + * 根据身份信息获取键值 + * + * @param claims 身份信息 + * @param key 键 + * @return 值 + */ + public static String getValue(Claims claims, String key) + { + return Convert.toStr(claims.get(key), ""); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/LogUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/LogUtils.java new file mode 100644 index 0000000..54df3f3 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.vetti.common.utils; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/MessageUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/MessageUtils.java new file mode 100644 index 0000000..9ac2039 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/MessageUtils.java @@ -0,0 +1,50 @@ +package com.vetti.common.utils; + +import cn.hutool.core.util.StrUtil; +import com.vetti.common.config.InternationalProperties; +import com.vetti.common.enums.InternationalLangTypeEnum; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.vetti.common.utils.spring.SpringUtils; + +import java.util.Properties; + +/** + * 获取i18n资源文件 + * + * @author wangxiangshun + */ +public class MessageUtils +{ + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) + { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } + + /** + * 根据消息键值参数 获取消息 + * + * @param code 消息键 + * @return 获取国际化翻译值 + */ + public static String messageCustomize(String code) + { + //获取语言类型 + InternationalProperties iProperties = SpringUtils.getBean(InternationalProperties.class); + // 获取客户语言环境 + String lang = iProperties.getLang(); + if(StrUtil.isEmpty(lang)){ + lang = InternationalLangTypeEnum.INTERNATIONAL_LANG_TYPE_ENUM_EN.getCode(); + } + Properties properties = I18nUtil.loadI18nProp(lang); + return properties.getProperty(code); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/PageUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/PageUtils.java new file mode 100644 index 0000000..07d0839 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/PageUtils.java @@ -0,0 +1,35 @@ +package com.vetti.common.utils; + +import com.github.pagehelper.PageHelper; +import com.vetti.common.core.page.PageDomain; +import com.vetti.common.core.page.TableSupport; +import com.vetti.common.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author ruoyi + */ +public class PageUtils extends PageHelper +{ + /** + * 设置请求分页数据 + */ + public static void startPage() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() + { + PageHelper.clearPage(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/SecurityUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/SecurityUtils.java new file mode 100644 index 0000000..59e3dc3 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/SecurityUtils.java @@ -0,0 +1,214 @@ +package com.vetti.common.utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import com.vetti.common.constant.SecurityConstants; +import com.vetti.common.constant.TokenConstants; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.PatternMatchUtils; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.HttpStatus; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.exception.ServiceException; + +import javax.servlet.http.HttpServletRequest; + +/** + * 安全服务工具类 + * + * @author ruoyi + */ +public class SecurityUtils +{ + + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(String permission) + { + return hasPermi(getLoginUser().getPermissions(), permission); + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 验证用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public static boolean hasRole(String role) + { + List roleList = getLoginUser().getUser().getRoles(); + Collection roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet()); + return hasRole(roles, role); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public static boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role)); + } + + /** + * 获取请求token + */ + public static String getToken() + { + return getToken(ServletUtils.getRequest()); + } + + /** + * 根据request获取请求token + */ + public static String getToken(HttpServletRequest request) + { + // 从header获取token标识 + String token = request.getHeader(SecurityConstants.AUTHORIZATION_HEADER); + return replaceTokenPrefix(token); + } + + /** + * 裁剪token前缀 + */ + public static String replaceTokenPrefix(String token) + { + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) + { + token = token.replaceFirst(TokenConstants.PREFIX, ""); + } + return token; + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/ServletUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/ServletUtils.java new file mode 100644 index 0000000..ec654f2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/ServletUtils.java @@ -0,0 +1,285 @@ +package com.vetti.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.alibaba.fastjson2.JSON; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.text.Convert; +import reactor.core.publisher.Mono; +import com.vetti.common.core.domain.R; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +public class ServletUtils +{ + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) + { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) + { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value) + { + return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value, int code) + { + return webFluxResponseWriter(response, HttpStatus.OK, value, code); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param status http状态码 + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) + { + return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param contentType content-type + * @param status http状态码 + * @param code 响应状态码 + * @param value 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) + { + response.setStatusCode(status); + response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType); + R result = R.fail(code, value.toString()); + DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes()); + return response.writeWith(Mono.just(dataBuffer)); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/StringUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/StringUtils.java new file mode 100644 index 0000000..17e02f7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/StringUtils.java @@ -0,0 +1,722 @@ +package com.vetti.common.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.springframework.util.AntPathMatcher; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** 星号 */ + private static final char ASTERISK = '*'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) + { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) + { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) + { + return (str == null ? "" : str.trim()); + } + + /** + * 替换指定字符串的指定区间内字符为"*" + * + * @param str 字符串 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @return 替换后的字符串 + */ + public static String hide(CharSequence str, int startInclude, int endExclude) + { + if (isEmpty(str)) + { + return NULLSTR; + } + final int strLength = str.length(); + if (startInclude > strLength) + { + return NULLSTR; + } + if (endExclude > strLength) + { + endExclude = strLength; + } + if (startInclude > endExclude) + { + // 如果起始位置大于结束位置,不替换 + return NULLSTR; + } + final char[] chars = new char[strLength]; + for (int i = 0; i < strLength; i++) + { + if (i >= startInclude && i < endExclude) + { + chars[i] = ASTERISK; + } + else + { + chars[i] = str.charAt(i); + } + } + return new String(chars); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) + { + if (str == null) + { + return NULLSTR; + } + + if (start < 0) + { + start = str.length() + start; + } + + if (start < 0) + { + start = 0; + } + if (start > str.length()) + { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) + { + if (str == null) + { + return NULLSTR; + } + + if (end < 0) + { + end = str.length() + end; + } + if (start < 0) + { + start = str.length() + start; + } + + if (end > str.length()) + { + end = str.length(); + } + + if (start > end) + { + return NULLSTR; + } + + if (start < 0) + { + start = 0; + } + if (end < 0) + { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 在字符串中查找第一个出现的 `open` 和最后一个出现的 `close` 之间的子字符串 + * + * @param str 要截取的字符串 + * @param open 起始字符串 + * @param close 结束字符串 + * @return 截取结果 + */ + public static String substringBetweenLast(final String str, final String open, final String close) + { + if (isEmpty(str) || isEmpty(open) || isEmpty(close)) + { + return NULLSTR; + } + final int start = str.indexOf(open); + if (start != INDEX_NOT_FOUND) + { + final int end = str.lastIndexOf(close); + if (end != INDEX_NOT_FOUND) + { + return str.substring(start + open.length(), end); + } + } + return NULLSTR; + } + + /** + * 判断是否为空,并且不是空白字符 + * + * @param str 要判断的value + * @return 结果 + */ + public static boolean hasText(String str) + { + return (str != null && !str.isEmpty() && containsText(str)); + } + + private static boolean containsText(CharSequence str) + { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) + { + if (!Character.isWhitespace(str.charAt(i))) + { + return true; + } + } + return false; + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) + { + if (isEmpty(params) || isEmpty(template)) + { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) + { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @return list集合 + */ + public static final List str2List(String str, String sep) + { + return str2List(str, sep, true, false); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) + { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) + { + return list; + } + String[] split = str.split(sep); + for (String string : split) + { + if (filterBlank && StringUtils.isBlank(string)) + { + continue; + } + if (trim) + { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) + { + if (isEmpty(collection) || isEmpty(array)) + { + return false; + } + else + { + for (String str : array) + { + if (collection.contains(str)) + { + return true; + } + } + return false; + } + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) + { + if (isEmpty(cs) || isEmpty(searchCharSequences)) + { + return false; + } + for (CharSequence testStr : searchCharSequences) + { + if (containsIgnoreCase(cs, testStr)) + { + return true; + } + } + return false; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) + { + if (str == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + if (i > 0) + { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } + else + { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) + { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) + { + sb.append(SEPARATOR); + } + else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) + { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) + { + if (str != null && strs != null) + { + for (String s : strs) + { + if (str.equalsIgnoreCase(trim(s))) + { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) + { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) + { + // 没必要转换 + return ""; + } + else if (!name.contains("_")) + { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) + { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) + { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 + * 例如:user_name->userName + */ + public static String toCamelCase(String s) + { + if (s == null) + { + return null; + } + if (s.indexOf(SEPARATOR) == -1) + { + return s; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + + if (c == SEPARATOR) + { + upperCase = true; + } + else if (upperCase) + { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } + else + { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) + { + if (isEmpty(str) || isEmpty(strs)) + { + return false; + } + for (String pattern : strs) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) + { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) + { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) + { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) + { + final StringBuilder sb = new StringBuilder(size); + if (s != null) + { + final int len = s.length(); + if (s.length() <= size) + { + for (int i = size - len; i > 0; i--) + { + sb.append(c); + } + sb.append(s); + } + else + { + return s.substring(len - size, len); + } + } + else + { + for (int i = size; i > 0; i--) + { + sb.append(c); + } + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/utils/Threads.java b/vetti-common/src/main/java/com/vetti/common/utils/Threads.java new file mode 100644 index 0000000..8393572 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.vetti.common.utils; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 线程相关工具类. + * + * @author ruoyi + */ +public class Threads +{ + private static final Logger logger = LoggerFactory.getLogger(Threads.class); + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) + { + try + { + Thread.sleep(milliseconds); + } + catch (InterruptedException e) + { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) + { + if (pool != null && !pool.isShutdown()) + { + pool.shutdown(); + try + { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + logger.info("Pool did not terminate"); + } + } + } + catch (InterruptedException ie) + { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) + { + if (t == null && r instanceof Future) + { + try + { + Future future = (Future) r; + if (future.isDone()) + { + future.get(); + } + } + catch (CancellationException ce) + { + t = ce; + } + catch (ExecutionException ee) + { + t = ee.getCause(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + if (t != null) + { + logger.error(t.getMessage(), t); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/UnitConvertUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/UnitConvertUtils.java new file mode 100644 index 0000000..cf060a1 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/UnitConvertUtils.java @@ -0,0 +1,157 @@ +package com.vetti.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Objects; + +/** + * 通用单位转换工具类 + * 支持:长度、重量、时间、面积、体积、温度 等常见单位转换 + * + * @author ID + * @date 2025/9/15 22:59 + */ +public class UnitConvertUtils { + + // ==================== 常量:单位换算系数(基于国际标准单位)==================== + // 长度单位:1米(m) = 对应单位的数量 + public static final BigDecimal M_TO_KM = new BigDecimal("0.001"); // 米 -> 千米 + public static final BigDecimal M_TO_CM = new BigDecimal("100"); // 米 -> 厘米 + public static final BigDecimal M_TO_MM = new BigDecimal("1000"); // 米 -> 毫米 + public static final BigDecimal M_TO_INCH = new BigDecimal("39.3701");// 米 -> 英寸 + public static final BigDecimal M_TO_FOOT = new BigDecimal("3.28084"); // 米 -> 英尺 + + // 重量单位:1千克(kg) = 对应单位的数量 + public static final BigDecimal KG_TO_G = new BigDecimal("1000"); // 千克 -> 克 + public static final BigDecimal KG_TO_T = new BigDecimal("0.001"); // 千克 -> 吨 + public static final BigDecimal KG_TO_LB = new BigDecimal("2.20462"); // 千克 -> 磅(常衡) + + // 时间单位:1秒(s) = 对应单位的数量(修正为小数形式,避免解析错误) + public static final BigDecimal S_TO_MS = new BigDecimal("1000"); // 秒 -> 毫秒 + public static final BigDecimal S_TO_MIN = new BigDecimal("0.016666666666666666"); // 秒 -> 分钟 (1/60) + public static final BigDecimal S_TO_HOUR = new BigDecimal("0.0002777777777777778"); // 秒 -> 小时 (1/3600) + public static final BigDecimal S_TO_DAY = new BigDecimal("0.000011574074074074073"); // 秒 -> 天 (1/86400) + + // 面积单位:1平方米(m²) = 对应单位的数量 + public static final BigDecimal M2_TO_CM2 = new BigDecimal("10000"); // 平方米 -> 平方厘米 + public static final BigDecimal M2_TO_KM2 = new BigDecimal("1e-6"); // 平方米 -> 平方千米 + public static final BigDecimal M2_TO_ACRE = new BigDecimal("0.000247105"); // 平方米 -> 英亩 + + // 温度单位:转换公式(摄氏度℃ ↔ 华氏度℉) + public static final BigDecimal C_TO_F_RATIO = new BigDecimal("1.8"); // ℃转℉系数 + public static final BigDecimal C_TO_F_OFFSET = new BigDecimal("32"); // ℃转℉偏移量 + + + /** + * 通用单位转换方法(适用于“系数乘法”类型的转换,如长度、重量、面积等) + * @param sourceValue 原始值(不可为null) + * @param convertRatio 转换系数(如米转千米用 M_TO_KM) + * @param scale 结果保留小数位数(默认2位) + * @return 转换后的值(BigDecimal,避免精度丢失) + */ + public static BigDecimal convertByRatio(BigDecimal sourceValue, BigDecimal convertRatio, Integer... scale) { + // 1. 参数校验 + Objects.requireNonNull(sourceValue, "原始值不能为null"); + Objects.requireNonNull(convertRatio, "转换系数不能为null"); + int resultScale = scale.length > 0 ? scale[0] : 2; + + // 校验小数位数是否合法 + if (resultScale < 0) { + throw new IllegalArgumentException("小数位数不能为负数: " + resultScale); + } + + // 2. 计算:原始值 × 转换系数,四舍五入 + return sourceValue.multiply(convertRatio).setScale(resultScale, RoundingMode.HALF_UP); + } + + + /** + * 温度转换(摄氏度 ↔ 华氏度) + * @param sourceValue 原始温度值(不可为null) + * @param sourceUnit 原始单位(支持 "C" 或 "F") + * @param targetUnit 目标单位(支持 "C" 或 "F") + * @param scale 结果保留小数位数(默认1位) + * @return 转换后的温度值 + */ + public static BigDecimal convertTemperature(BigDecimal sourceValue, String sourceUnit, String targetUnit, Integer... scale) { + // 1. 参数校验 + Objects.requireNonNull(sourceValue, "温度值不能为null"); + Objects.requireNonNull(sourceUnit, "原始单位不能为null"); + Objects.requireNonNull(targetUnit, "目标单位不能为null"); + + if (!("C".equals(sourceUnit) || "F".equals(sourceUnit))) { + throw new IllegalArgumentException("原始单位仅支持 'C'(摄氏度)或 'F'(华氏度),实际为: " + sourceUnit); + } + if (!("C".equals(targetUnit) || "F".equals(targetUnit))) { + throw new IllegalArgumentException("目标单位仅支持 'C'(摄氏度)或 'F'(华氏度),实际为: " + targetUnit); + } + + int resultScale = scale.length > 0 ? scale[0] : 1; + if (resultScale < 0) { + throw new IllegalArgumentException("小数位数不能为负数: " + resultScale); + } + + if (sourceUnit.equals(targetUnit)) { + return sourceValue.setScale(resultScale, RoundingMode.HALF_UP); + } + + // 2. 转换逻辑 + BigDecimal result; + if ("C".equals(sourceUnit) && "F".equals(targetUnit)) { + // 摄氏度 → 华氏度:F = C × 1.8 + 32 + result = sourceValue.multiply(C_TO_F_RATIO).add(C_TO_F_OFFSET); + } else { + // 华氏度 → 摄氏度:C = (F - 32) / 1.8 + result = sourceValue.subtract(C_TO_F_OFFSET) + .divide(C_TO_F_RATIO, 10, RoundingMode.HALF_UP); + } + + // 3. 保留小数位数 + return result.setScale(resultScale, RoundingMode.HALF_UP); + } + + + // ==================== 便捷方法:简化常用转换调用 ==================== + /** + * 米 → 千米 + */ + public static BigDecimal meterToKilometer(BigDecimal meter, Integer... scale) { + return convertByRatio(meter, M_TO_KM, scale); + } + + /** + * 千克 → 磅 + */ + public static BigDecimal kilogramToPound(BigDecimal kg, Integer... scale) { + return convertByRatio(kg, KG_TO_LB, scale); + } + + /** + * 秒 → 分钟 + */ + public static BigDecimal secondToMinute(BigDecimal second, Integer... scale) { + return convertByRatio(second, S_TO_MIN, scale); + } + + /** + * 秒 → 小时 + */ + public static BigDecimal secondToHour(BigDecimal second, Integer... scale) { + return convertByRatio(second, S_TO_HOUR, scale); + } + + /** + * 摄氏度 → 华氏度 + */ + public static BigDecimal celsiusToFahrenheit(BigDecimal celsius, Integer... scale) { + return convertTemperature(celsius, "C", "F", scale); + } + + /** + * 华氏度 → 摄氏度 + */ + public static BigDecimal fahrenheitToCelsius(BigDecimal fahrenheit, Integer... scale) { + return convertTemperature(fahrenheit, "F", "C", scale); + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/bean/BeanUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/bean/BeanUtils.java new file mode 100644 index 0000000..00fd76a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.vetti.common.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author ruoyi + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/bean/BeanValidators.java b/vetti-common/src/main/java/com/vetti/common/utils/bean/BeanValidators.java new file mode 100644 index 0000000..7001752 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.vetti.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/email/EmailUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/email/EmailUtil.java new file mode 100644 index 0000000..d065d55 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/email/EmailUtil.java @@ -0,0 +1,114 @@ +package com.vetti.common.utils.email; + +import com.vetti.common.config.TwilioConfig; +import com.vetti.common.entity.verification.BaseTemplateEmail; +import com.vetti.common.utils.MessageUtils; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.sendgrid.Method; +import com.sendgrid.Request; +import com.sendgrid.Response; +import com.sendgrid.SendGrid; +import com.sendgrid.helpers.mail.Mail; +import com.sendgrid.helpers.mail.objects.Content; +import com.sendgrid.helpers.mail.objects.Email; +import com.sendgrid.helpers.mail.objects.Personalization; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; + +/** + * @author ID + * @date 2025/8/29 22:36 + */ +@Component +public class EmailUtil { + + @Resource + private TwilioConfig twilioConfig; + + /** + * 发邮件 + * + * @param toEmail + * @param subject + * @param content + * @throws IOException + */ + public void sendEmail(String toEmail, String subject, String content) throws IOException { + Email from = new Email(twilioConfig.getFromEmail(), twilioConfig.getFromName()); + Email to = new Email(toEmail); + Content emailContent = new Content("text/plain", content); + Mail mail = new Mail(from, subject, to, emailContent); + + SendGrid sg = new SendGrid(twilioConfig.getApiKey()); + Request request = new Request(); + + request.setMethod(Method.POST); + request.setEndpoint("mail/send"); + request.setBody(mail.build()); + + Response response = sg.api(request); + + // 可以根据响应状态做进一步处理 + if (response.getStatusCode() >= 400) { + throw new RuntimeException(MessageUtils.messageCustomize("systemEmailUtil10001")+": " + response.getBody()); + } + } + + public void sendEmail(String toEmail, String templateId, BaseTemplateEmail templateEmail) throws IOException { + Email from = new Email(twilioConfig.getFromEmail(), twilioConfig.getFromName()); + Email to = new Email(toEmail); + Content emailContent = new Content("text/html", " "); + + // 关键修改:使用无参构造函数,避免自动创建Personalization + Mail mail = new Mail(); + mail.setFrom(from); // 手动设置发件人 + mail.addContent(emailContent); // 手动添加内容 + + // 处理模板和个性化配置(此时只会添加一个Personalization) + template(to, mail, templateId, templateEmail); + + SendGrid sg = new SendGrid(twilioConfig.getApiKey()); + Request request = new Request(); + request.setMethod(Method.POST); + request.setEndpoint("mail/send"); + request.setBody(mail.build()); + Response response = sg.api(request); + + if (response.getStatusCode() >= 400) { + throw new RuntimeException(MessageUtils.messageCustomize("systemEmailUtil10001")+": " + response.getBody()); + } + } + + private void template(Email to, Mail mail, String templateId, BaseTemplateEmail templateEmail) { + mail.setTemplateId(templateId); + Personalization personalization = new Personalization(); + personalization.addTo(to); // 仅添加一次收件人 + + // 处理模板变量 + JsonObject jsonO = templateEmail.toJsonObject(); + if (jsonO != null) { + jsonO.entrySet().forEach(entry -> { + String key = entry.getKey(); + JsonElement value = entry.getValue(); + if (value.isJsonPrimitive()) { + if (value.getAsJsonPrimitive().isString()) { + personalization.addDynamicTemplateData(key, value.getAsString()); + } else if (value.getAsJsonPrimitive().isNumber()) { + personalization.addDynamicTemplateData(key, value.getAsNumber()); + } else if (value.getAsJsonPrimitive().isBoolean()) { + personalization.addDynamicTemplateData(key, value.getAsBoolean()); + } + } else { + System.out.printf("键: %s, 非基本类型值: %s%n", key, value); + } + }); + } + + // 仅添加一次Personalization + mail.addPersonalization(personalization); + } + +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/file/FileTypeUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..8252e5a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.vetti.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: ruoyi.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: ruoyi.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/utils/file/FileUploadUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..a4f0912 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/file/FileUploadUtils.java @@ -0,0 +1,260 @@ +package com.vetti.common.utils.file; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.Constants; +import com.vetti.common.exception.file.FileNameLengthLimitExceededException; +import com.vetti.common.exception.file.FileSizeLimitExceededException; +import com.vetti.common.exception.file.InvalidExtensionException; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.uuid.IdUtils; +import com.vetti.common.utils.uuid.Seq; + +/** + * 文件上传工具类 + * + * @author ruoyi + */ +public class FileUploadUtils +{ + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = RuoYiConfig.getProfile(); + + public static void setDefaultBaseDir(String defaultBaseDir) + { + FileUploadUtils.defaultBaseDir = defaultBaseDir; + } + + public static String getDefaultBaseDir() + { + return defaultBaseDir; + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件名称 + * @throws Exception + */ + public static final String upload(MultipartFile file) throws IOException + { + try + { + return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file) throws IOException + { + try + { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + return upload(baseDir, file, allowedExtension, false); + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param useCustomNaming 系统自定义文件名 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); + } + + /** + * 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀) + */ + public static final String extractFilename(MultipartFile file) + { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + /** + * 编编码文件名(日期格式目录 + UUID + 后缀) + */ + public static final String uuidFilename(MultipartFile file) + { + return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file)); + } + + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) + { + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final String getPathFileName(String uploadDir, String fileName) throws IOException + { + int dirLastIndex = RuoYiConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) + { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } + else + { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) + { + for (String str : allowedExtension) + { + if (str.equalsIgnoreCase(extension)) + { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) + { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) + { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/file/FileUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/file/FileUtils.java new file mode 100644 index 0000000..5c5c13a --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/file/FileUtils.java @@ -0,0 +1,352 @@ +package com.vetti.common.utils.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.Constants; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.uuid.IdUtils; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件处理工具类 + * + * @author ruoyi + */ +public class FileUtils +{ + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + // 新增:文件名清洗方法(核心功能:过滤特殊字符,避免路径注入和文件名异常) + public static String cleanFileName(String fileName) { + if (fileName == null || fileName.trim().isEmpty()) { + return "unknown_file"; // 空文件名默认值 + } + // 1. 过滤Windows和Linux系统中的非法字符(如 \ / : * ? " < > | 等) + String cleanedName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_"); + // 2. 去除文件名前后空格,避免空白字符导致的存储问题 + cleanedName = cleanedName.trim(); + // 3. 限制文件名长度(可选,避免超过操作系统或存储服务的文件名长度限制) + int maxLength = 255; // 大多数文件系统支持的最大文件名长度 + if (cleanedName.length() > maxLength) { + // 保留文件扩展名,截取文件名前缀 + String extension = FilenameUtils.getExtension(cleanedName); + String baseName = FilenameUtils.getBaseName(cleanedName); + // 截取前缀 + 后缀(确保总长度不超过maxLength) + baseName = baseName.substring(0, Math.min(baseName.length(), maxLength - (extension.isEmpty() ? 0 : extension.length() + 1))); + cleanedName = extension.isEmpty() ? baseName : baseName + "." + extension; + } + return cleanedName; + } + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException + { + FileInputStream fis = null; + try + { + File file = new File(filePath); + if (!file.exists()) + { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) + { + os.write(b, 0, length); + } + } + catch (IOException e) + { + throw e; + } + finally + { + IOUtils.close(os); + IOUtils.close(fis); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException + { + return writeBytes(data, RuoYiConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException + { + FileOutputStream fos = null; + String pathName = ""; + try + { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } + finally + { + IOUtils.close(fos); + } + return FileUploadUtils.getPathFileName(uploadDir, pathName); + } + + /** + * 移除路径中的请求前缀片段 + * + * @param filePath 文件路径 + * @return 移除后的文件路径 + */ + public static String stripPrefix(String filePath) + { + return StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) + { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) + { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) + { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) + { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) + { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) + { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException + { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) + { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } + else if (agent.contains("Firefox")) + { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } + else if (agent.contains("Chrome")) + { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + else + { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException + { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException + { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "gif"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "jpg"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "bmp"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public static String getName(String fileName) + { + if (fileName == null) + { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) + { + if (fileName == null) + { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } + + public static File convert(MultipartFile multipartFile) throws IOException { + // 1. 创建临时文件(指定前缀、后缀和临时目录) + // 前缀:文件前缀名;后缀:保持与原文件一致(如.jpg);目录:默认临时目录 + File tempFile = File.createTempFile( + "temp_", + getFileExtension(multipartFile.getOriginalFilename()), + new File(System.getProperty("java.io.tmpdir")) // 系统临时目录 + ); + + // 2. 将MultipartFile内容写入临时文件 + multipartFile.transferTo(tempFile); + + // 3. 标记临时文件在JVM退出时删除(可选,根据业务需求) + tempFile.deleteOnExit(); + + return tempFile; + } + + // 辅助方法:获取文件后缀(如".jpg") + private static String getFileExtension(String fileName) { + if (fileName == null || !fileName.contains(".")) { + return ""; // 无后缀时返回空 + } + return fileName.substring(fileName.lastIndexOf(".")); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/file/ImageUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/file/ImageUtils.java new file mode 100644 index 0000000..c00b82d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/file/ImageUtils.java @@ -0,0 +1,98 @@ +package com.vetti.common.utils.file; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.Constants; +import com.vetti.common.utils.StringUtils; + +/** + * 图片处理工具类 + * + * @author ruoyi + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + if (url.startsWith("http")) + { + // 网络地址 + URL urlObj = new URL(url); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + } + else + { + // 本机地址 + String localPath = RuoYiConfig.getProfile(); + String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); + in = new FileInputStream(downloadPath); + } + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("获取文件路径异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/file/MimeTypeUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..7917f84 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.vetti.common.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/file/MinioUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/file/MinioUtil.java new file mode 100644 index 0000000..1e3d3c4 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/file/MinioUtil.java @@ -0,0 +1,613 @@ +package com.vetti.common.utils.file; + +import com.vetti.common.core.domain.MinioFileInfo; +import com.vetti.common.utils.DateUtils; +import io.minio.*; +import io.minio.errors.ErrorResponseException; +import io.minio.http.Method; +import io.minio.messages.Bucket; +import io.minio.messages.DeleteError; +import io.minio.messages.DeleteObject; +import io.minio.messages.Item; +import lombok.extern.log4j.Log4j2; +import org.springframework.util.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Log4j2 +@Component +public class MinioUtil { + + // 默认预签名URL有效期:7天(单位:秒) + private static final int DEFAULT_EXPIRES = 60 * 60 * 24 * 7; + + // 默认最大文件大小:100MB + @Value("${fs.minio.max-file-size}") + private long maxFileSize; + + @Autowired + private MinioClient minioClient; + + @Value("${fs.minio.endpoint}") + private String endpoint; + + /** + * 获取所有存储桶名称 + * + * @return 存储桶名称列表 + */ + public List getAllBucketNames() { + List bucketNames = new ArrayList<>(); + try { + // 获取所有桶信息 + List buckets = minioClient.listBuckets(); + + // 提取桶名称 + for (Bucket bucket : buckets) { + bucketNames.add(bucket.name()); + } + + log.info("成功获取所有存储桶名称,共{}个", bucketNames.size()); + } catch (Exception e) { + log.error("获取存储桶列表失败", e); + throw new RuntimeException("获取存储桶列表失败: " + e.getMessage()); + } + return bucketNames; + } + + /** + * 检查存储桶是否存在,不存在则创建 + */ + public void checkAndCreateBucket(String bucketName) { + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + try { + boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); + if (!exists) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + log.info("存储桶[{}]创建成功", bucketName); + + // 3. 配置桶策略为 "public-read"(核心:允许匿名读) + // 策略语句:允许所有用户(*)对桶内所有文件(/*)执行读操作(s3:GetObject) + String policyJson = "{\n" + + " \"Version\": \"2012-10-17\",\n" + + " \"Statement\": [\n" + + " {\n" + + " \"Effect\": \"Allow\",\n" + + " \"Principal\": \"*\",\n" + + " \"Action\": \"s3:GetObject\",\n" + + " \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"\n" + + " }\n" + + " ]\n" + + "}"; + + // 应用策略到桶 + minioClient.setBucketPolicy(SetBucketPolicyArgs.builder() + .bucket(bucketName) + .config(policyJson) + .build()); + log.info("桶策略配置成功,现在文件可匿名访问"); + } else { + log.info("存储桶[{}]已存在", bucketName); + } + } catch (Exception e) { + log.error("检查或创建存储桶[{}]失败", bucketName, e); + throw new RuntimeException("操作存储桶失败: " + e.getMessage()); + } + } + + public String objectName(Long loginUserId) { + DateUtils.parseDateToStr(DateUtils.YYYYMMDD, DateUtils.getNowDate()); + return loginUserId + "/" + DateUtils.parseDateToStr(DateUtils.YYYYMMDD, DateUtils.getNowDate()) + "/"; + } + + /** + * 上传文件 + * + * @param file 上传的文件 + * @param objectName 存储在 MinIO 中的文件名(可包含路径,如 "images/xxx.jpg") + * @param bucketName 存储桶名称 + * @return 文件访问 URL + */ + public String uploadFile(MultipartFile file, String objectName, String bucketName) { + // 参数校验 + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("上传文件不能为空"); + } + if (file.getSize() > maxFileSize) { + throw new IllegalArgumentException("文件大小超过限制,最大支持" + maxFileSize / (1024 * 1024) + "MB"); + } + if (StringUtils.isEmpty(objectName)) { + throw new IllegalArgumentException("存储文件名不能为空"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + objectName = objectName + System.currentTimeMillis() + "." + FileTypeUtils.getFileType(file.getOriginalFilename()); + try { + // 检查存储桶 + checkAndCreateBucket(bucketName); + + // 上传文件 + minioClient.putObject( + PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .stream(file.getInputStream(), file.getSize(), -1) + .contentType(file.getContentType()) + .build() + ); + + String fileUrl = String.format("%s/%s/%s", endpoint, bucketName, objectName); + log.info("文件[{}]上传成功,访问地址:{}", objectName, fileUrl); + return objectName; + } catch (Exception e) { + log.error("文件[{}]上传失败", objectName, e); + throw new RuntimeException("文件上传失败: " + e.getMessage()); + } + } + + /** + * 上传文件(带自定义元数据) + */ + public String uploadFileWithMetadata(MultipartFile file, String objectName, String bucketName, Map metadata) { + // 参数校验 + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("上传文件不能为空"); + } + if (file.getSize() > maxFileSize) { + throw new IllegalArgumentException("文件大小超过限制,最大支持" + maxFileSize / (1024 * 1024) + "MB"); + } + if (StringUtils.isEmpty(objectName)) { + throw new IllegalArgumentException("存储文件名不能为空"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + try { + // 检查存储桶 + checkAndCreateBucket(bucketName); + + // 构建上传参数 + PutObjectArgs.Builder builder = PutObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .stream(file.getInputStream(), file.getSize(), -1) + .contentType(file.getContentType()); + + // 添加元数据 - 修正为使用userMetadata方法 + if (metadata != null && !metadata.isEmpty()) { + builder.userMetadata(metadata); + } + + // 上传文件 + minioClient.putObject(builder.build()); + + String fileUrl = String.format("%s/%s/%s", endpoint, bucketName, objectName); + log.info("文件[{}]上传成功,访问地址:{}", objectName, fileUrl); + return fileUrl; + } catch (Exception e) { + log.error("文件[{}]上传失败", objectName, e); + throw new RuntimeException("文件上传失败: " + e.getMessage()); + } + } + + /** + * 下载文件 + * + * @param objectName 存储在 MinIO 中的文件名 + * @param response 响应对象 + * @param bucketName 存储桶名称 + */ + public void downloadFile(String objectName, HttpServletResponse response, String bucketName) { + if (StringUtils.isEmpty(objectName)) { + throw new IllegalArgumentException("文件名称不能为空"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + if (response == null) { + throw new IllegalArgumentException("响应对象不能为空"); + } + + try (InputStream stream = minioClient.getObject( + GetObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build()); + OutputStream os = response.getOutputStream()) { + + // 获取文件名 + String fileName = objectName.substring(objectName.lastIndexOf("/") + 1); + String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); + + // 设置响应头 + response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName); + response.setHeader("Content-Type", "application/octet-stream"); + response.setHeader("Content-Length", String.valueOf(stream.available())); + + // 写入响应 + byte[] buffer = new byte[1024]; + int len; + while ((len = stream.read(buffer)) > 0) { + os.write(buffer, 0, len); + } + os.flush(); + log.info("文件[{}]下载成功", objectName); + } catch (Exception e) { + log.error("文件[{}]下载失败", objectName, e); + throw new RuntimeException("文件下载失败: " + e.getMessage()); + } + } + + /** + * 删除文件 + * + * @param objectName 存储在 MinIO 中的文件名 + * @param bucketName 存储桶名称 + */ + public void deleteFile(String objectName, String bucketName) { + if (StringUtils.isEmpty(objectName)) { + throw new IllegalArgumentException("文件名称不能为空"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + try { + if (fileExists(objectName, bucketName)) { + minioClient.removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + log.info("文件[{}]删除成功", objectName); + } else { + log.warn("文件[{}]不存在,无需删除", objectName); + } + } catch (Exception e) { + log.error("文件[{}]删除失败", objectName, e); + throw new RuntimeException("文件删除失败: " + e.getMessage()); + } + } + + /** + * 批量删除文件 + * + * @param objectNames 存储在 MinIO 中的文件名列表 + * @param bucketName 存储桶名称 + */ + public void deleteFiles(List objectNames, String bucketName) { + if (objectNames == null || objectNames.isEmpty()) { + log.warn("批量删除的文件列表为空,无需操作"); + return; + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + try { + List deleteObjects = new ArrayList<>(); + for (String objectName : objectNames) { + if (StringUtils.hasText(objectName)) { + deleteObjects.add(new DeleteObject(objectName)); + } + } + + if (!deleteObjects.isEmpty()) { + Iterable> results = minioClient.removeObjects( + RemoveObjectsArgs.builder() + .bucket(bucketName) + .objects(deleteObjects) + .build() + ); + + // 处理删除结果 + for (Result result : results) { + DeleteError error = result.get(); + log.error("文件[{}]删除失败: {}", error.objectName(), error.message()); + } + log.info("批量删除完成,共处理{}个文件", deleteObjects.size()); + } + } catch (Exception e) { + log.error("批量删除文件失败", e); + throw new RuntimeException("批量删除文件失败: " + e.getMessage()); + } + } + + /** + * 获取存储桶内所有文件信息列表 + */ + public List listFiles(String bucketName) { + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + List fileList = new ArrayList<>(); + try { + Iterable> results = minioClient.listObjects( + ListObjectsArgs.builder() + .bucket(bucketName) + .recursive(true) + .build() + ); + + for (Result result : results) { + Item item = result.get(); + MinioFileInfo fileInfo = new MinioFileInfo(); + fileInfo.setFileName(item.objectName()); + fileInfo.setSize(item.size()); + fileInfo.setLastModified(item.lastModified()); + fileInfo.setDirectory(item.isDir()); + fileList.add(fileInfo); + } + log.info("获取存储桶[{}]文件列表成功,共{}个文件", bucketName, fileList.size()); + } catch (Exception e) { + log.error("获取存储桶[{}]文件列表失败", bucketName, e); + throw new RuntimeException("获取文件列表失败: " + e.getMessage()); + } + return fileList; + } + + /** + * 获取文件临时访问 URL(使用默认有效期7天) + */ + public String getPresignedUrl(String objectName, String bucketName) { + return getPresignedUrl(objectName, DEFAULT_EXPIRES, bucketName); + } + + /** + * 获取文件临时访问 URL + * + * @param objectName 存储在 MinIO 中的文件名 + * @param expires 有效期(秒) + * @param bucketName 存储桶名称 + * @return 临时访问 URL + */ + public String getPresignedUrl(String objectName, int expires, String bucketName) { + if (StringUtils.isEmpty(objectName)) { + throw new IllegalArgumentException("文件名称不能为空"); + } + if (expires <= 0) { + throw new IllegalArgumentException("有效期必须大于0"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + try { + if (!fileExists(objectName, bucketName)) { + log.warn("文件[{}]不存在,无法生成预签名URL", objectName); + return null; + } + + String url = minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(objectName) + .expiry(expires) + .build() + ); + log.info("文件[{}]预签名URL生成成功,有效期{}秒", objectName, expires); + return url; + } catch (Exception e) { + log.error("文件[{}]预签名URL生成失败", objectName, e); + throw new RuntimeException("生成预签名URL失败: " + e.getMessage()); + } + } + + /** + * 获取预签名URL(支持不同HTTP方法) + */ + public String getPresignedUrl(String objectName, int expires, String bucketName, Method method) { + if (method == null) { + throw new IllegalArgumentException("HTTP方法不能为空"); + } + if (StringUtils.isEmpty(objectName)) { + throw new IllegalArgumentException("文件名称不能为空"); + } + if (expires <= 0) { + throw new IllegalArgumentException("有效期必须大于0"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + try { + String url = minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(method) + .bucket(bucketName) + .object(objectName) + .expiry(expires) + .build() + ); + log.info("文件[{}]预签名URL({})生成成功,有效期{}秒", objectName, method, expires); + return url; + } catch (Exception e) { + log.error("文件[{}]预签名URL生成失败", objectName, e); + throw new RuntimeException("生成预签名URL失败: " + e.getMessage()); + } + } + + /** + * 检查文件是否存在 + */ + public boolean fileExists(String objectName, String bucketName) { + if (StringUtils.isEmpty(objectName)) { + return false; + } + if (StringUtils.isEmpty(bucketName)) { + return false; + } + + try { + minioClient.statObject( + StatObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + return true; + } catch (ErrorResponseException e) { + // 404错误表示文件不存在 + if (e.errorResponse().code().equals("NoSuchKey")) { + return false; + } + log.error("检查文件[{}]存在性时发生错误", objectName, e); + return false; + } catch (Exception e) { + log.error("检查文件[{}]存在性时发生错误", objectName, e); + return false; + } + } + + /** + * 复制文件 + * + * @param sourceBucketName 源存储桶 + * @param sourceObjectName 源文件名称 + * @param targetBucketName 目标存储桶 + * @param targetObjectName 目标文件名称 + */ + public void copyObject(String sourceBucketName, String sourceObjectName, + String targetBucketName, String targetObjectName) { + if (StringUtils.isEmpty(sourceBucketName) || StringUtils.isEmpty(sourceObjectName) || + StringUtils.isEmpty(targetBucketName) || StringUtils.isEmpty(targetObjectName)) { + throw new IllegalArgumentException("复制文件参数不能为空"); + } + + try { + // 检查源文件是否存在 + if (!fileExists(sourceObjectName, sourceBucketName)) { + throw new RuntimeException("源文件不存在: " + sourceObjectName); + } + + // 检查目标存储桶 + checkAndCreateBucket(targetBucketName); + + // 执行复制 + minioClient.copyObject( + CopyObjectArgs.builder() + .source( + CopySource.builder() + .bucket(sourceBucketName) + .object(sourceObjectName) + .build() + ) + .bucket(targetBucketName) + .object(targetObjectName) + .build() + ); + log.info("文件复制成功: {}/{} -> {}/{}", + sourceBucketName, sourceObjectName, + targetBucketName, targetObjectName); + } catch (Exception e) { + log.error("文件复制失败", e); + throw new RuntimeException("文件复制失败: " + e.getMessage()); + } + } + + /** + * 移动文件(本质是复制后删除原文件) + */ + public void moveObject(String sourceBucketName, String sourceObjectName, + String targetBucketName, String targetObjectName) { + try { + // 先复制 + copyObject(sourceBucketName, sourceObjectName, targetBucketName, targetObjectName); + // 再删除原文件 + deleteFile(sourceObjectName, sourceBucketName); + log.info("文件移动成功: {}/{} -> {}/{}", + sourceBucketName, sourceObjectName, + targetBucketName, targetObjectName); + } catch (Exception e) { + log.error("文件移动失败", e); + throw new RuntimeException("文件移动失败: " + e.getMessage()); + } + } + + /** + * 创建模拟文件夹(MinIO中实际是创建一个以/结尾的空对象) + */ + public void createDirectory(String directoryName, String bucketName) { + if (StringUtils.isEmpty(directoryName)) { + throw new IllegalArgumentException("文件夹名称不能为空"); + } + if (StringUtils.isEmpty(bucketName)) { + throw new IllegalArgumentException("存储桶名称不能为空"); + } + + // 确保文件夹名称以/结尾 + if (!directoryName.endsWith("/")) { + directoryName += "/"; + } + + try { + // 检查文件夹是否已存在 + if (fileExists(directoryName, bucketName)) { + log.info("文件夹[{}]已存在", directoryName); + return; + } + + // 创建空对象模拟文件夹 + minioClient.putObject( + PutObjectArgs.builder() + .bucket(bucketName) + .object(directoryName) + .stream(null, 0, -1) + .build() + ); + log.info("文件夹[{}]创建成功", directoryName); + } catch (Exception e) { + log.error("文件夹[{}]创建失败", directoryName, e); + throw new RuntimeException("创建文件夹失败: " + e.getMessage()); + } + } + + /** + * 获取文件信息 + */ + public MinioFileInfo getFileInfo(String objectName, String bucketName) { + if (StringUtils.isEmpty(objectName) || StringUtils.isEmpty(bucketName)) { + return null; + } + + try { + StatObjectResponse stat = minioClient.statObject( + StatObjectArgs.builder() + .bucket(bucketName) + .object(objectName) + .build() + ); + + MinioFileInfo fileInfo = new MinioFileInfo(); + fileInfo.setFileName(objectName); + fileInfo.setSize(stat.size()); + fileInfo.setLastModified(stat.lastModified()); + fileInfo.setContentType(stat.contentType()); + fileInfo.setDirectory(false); + fileInfo.setMetadata(stat.userMetadata()); + + return fileInfo; + } catch (Exception e) { + log.error("获取文件[{}]信息失败", objectName, e); + return null; + } + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapRoutingService.java b/vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapRoutingService.java new file mode 100644 index 0000000..267dff3 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapRoutingService.java @@ -0,0 +1,200 @@ +//package com.fleet.common.utils.hereMap; +// +//import com.fasterxml.jackson.databind.JsonNode; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.fleet.common.config.HereMapsProperties; +//import com.fleet.common.entity.hereMap.TruckRoute; +//import lombok.extern.slf4j.Slf4j; +//import org.apache.http.client.utils.URIBuilder; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Service; +// +//import javax.annotation.Resource; +//import java.net.URI; +//import java.net.URLEncoder; +//import java.net.http.HttpClient; +//import java.net.http.HttpRequest; +//import java.net.http.HttpResponse; +//import java.nio.charset.StandardCharsets; +//import java.time.Duration; +//import java.util.ArrayList; +//import java.util.List; +// +///** +// * @author ID +// * @date 2025/9/2 20:44 +// */ +//@Service +//@Slf4j +//public class HereMapRoutingService { +// +// @Resource +// private HereMapsProperties hereMapsProperties; +// +// @Resource +// private HttpClient httpClient; +// +// @Resource +// private ObjectMapper objectMapper; +// +// @Value("${http.client.connect-timeout-seconds:10}") +// private Integer connectTimeoutSeconds; +// +// +// /** +// * 计算卡车路线,支持所有参数 +// * +// * @param truckRoute 包含所有卡车参数的请求对象 +// * @return 路线规划结果(JSON节点) +// * @throws Exception 处理过程中发生的异常 +// */ +// public JsonNode calculateTruckRoute(TruckRoute truckRoute) throws Exception { +// log.info("开始计算卡车路线,起点: {}, 终点: {}, 是否运输危化品: {}", +// truckRoute.getStart(), truckRoute.getEnd(), truckRoute.isHazardous()); +// +// // 构建查询参数 +// List queryParams = new ArrayList<>(); +// +// // 基本路线参数 +// queryParams.add("apiKey=" + URLEncoder.encode(hereMapsProperties.getApiKey(), StandardCharsets.UTF_8.name())); +// queryParams.add("origin=" + URLEncoder.encode(truckRoute.getStart(), StandardCharsets.UTF_8.name())); +// queryParams.add("destination=" + URLEncoder.encode(truckRoute.getEnd(), StandardCharsets.UTF_8.name())); +// queryParams.add("transportMode=truck"); +// +// // 路线偏好 +// if (truckRoute.getRoutePreference() != null && !truckRoute.getRoutePreference().isEmpty()) { +// queryParams.add("routingMode=" + truckRoute.getRoutePreference()); +// } else { +// queryParams.add("routingMode=balanced"); // 默认值 +// } +// +// // 车辆参数 +// if (truckRoute.getHeight() != null) { +// queryParams.add("truck[height]=" + truckRoute.getHeight() + "m"); +// } +// if (truckRoute.getWidth() != null) { +// queryParams.add("truck[width]=" + truckRoute.getWidth() + "m"); +// } +// if (truckRoute.getLength() != null) { +// queryParams.add("truck[length]=" + truckRoute.getLength() + "m"); +// } +// if (truckRoute.getWeightTotal() != null) { +// queryParams.add("truck[totalWeight]=" + truckRoute.getWeightTotal() + "t"); +// } +// if (truckRoute.getWeightPerAxle() != null) { +// queryParams.add("truck[axleWeight]=" + truckRoute.getWeightPerAxle() + "t"); +// } +// if (truckRoute.getAxes() != null) { +// queryParams.add("truck[axes]=" + truckRoute.getAxes()); +// } +// if (truckRoute.getTrailers() != null) { +// queryParams.add("truck[trailers]=" + truckRoute.getTrailers()); +// } +// if (truckRoute.getMaxGradient() != null) { +// queryParams.add("truck[maxGradient]=" + truckRoute.getMaxGradient()); +// } +// if (truckRoute.getMaxSpeed() != null) { +// queryParams.add("truck[maxSpeed]=" + truckRoute.getMaxSpeed() + "km/h"); +// } +// +// // 避开选项 +// List avoidFeatures = new ArrayList<>(); +// if (truckRoute.isAvoidHighways()) avoidFeatures.add("highway"); +// if (truckRoute.isAvoidTolls()) avoidFeatures.add("tollroad"); +// if (truckRoute.isAvoidFerries()) avoidFeatures.add("ferry"); +// if (truckRoute.isAvoidTunnels()) avoidFeatures.add("tunnel"); +// if (truckRoute.isAvoidBorders()) avoidFeatures.add("borderCrossing"); +// if (truckRoute.isAvoidDirtRoads()) avoidFeatures.add("dirtRoad"); +// +// if (!avoidFeatures.isEmpty()) { +// queryParams.add("avoid[features]=" + String.join(",", avoidFeatures)); +// } +// +// // 危化品参数 +// if (truckRoute.isHazardous()) { +// queryParams.add("truck[hazmat]=true"); +// +// // 危化品类别 +// if (truckRoute.getHazmatTypes() != null && !truckRoute.getHazmatTypes().isEmpty()) { +// queryParams.add("truck[hazmatTypes]=" + String.join(",", truckRoute.getHazmatTypes())); +// } +// +// // 隧道限制代码 +// if (truckRoute.getHazmatTunnelRestrictionCode() != null && +// !truckRoute.getHazmatTunnelRestrictionCode().isEmpty()) { +// queryParams.add("truck[hazmatTunnelRestrictionCode]=" + +// truckRoute.getHazmatTunnelRestrictionCode()); +// } +// +// // 爆炸物和放射性物质 +// if (truckRoute.isHazmatIsExplosive()) { +// queryParams.add("truck[hazmatIsExplosive]=true"); +// } +// if (truckRoute.isHazmatIsRadioactive()) { +// queryParams.add("truck[hazmatIsRadioactive]=true"); +// } +// } +// +// // 构建完整URL +// String queryString = String.join("&", queryParams); +// String requestUri = hereMapsProperties.getRoutingApiUrl() + "?" + queryString; +// log.info("HERE Maps API请求URL: {}", requestUri); +// +// // 创建HTTP请求 +//// HttptruckRoute httptruckRoute = HttptruckRoute.newBuilder() +//// .uri(URI.create(fullUrl)) +//// .timeout(Duration.ofSeconds(15)) +//// .header("Accept", "application/json") +//// .GET() +//// .build(); +// +// // 发送请求并获取响应 +//// HttpResponse response = httpClient.send( +//// httptruckRoute, +//// HttpResponse.BodyHandlers.ofString() +//// ); +// +// HttpRequest request = buildHttpRequest(new URIBuilder(requestUri).build()); +// HttpResponse response = sendHttpRequest(request); +// +// // 处理响应状态 +// if (response.statusCode() != 200) { +// log.error("HERE Maps API请求失败,状态码: {}, 响应内容: {}", +// response.statusCode(), response.body()); +// throw new RuntimeException("路线规划失败,状态码: " + response.statusCode()); +// } +// +// // 解析JSON响应 +// JsonNode responseNode = objectMapper.readTree(response.body()); +// log.info("卡车路线规划成功,找到 {} 条路线", +// responseNode.get("routes").size()); +// +// return responseNode; +// } +// +// private HttpRequest buildHttpRequest(URI uri) { +// return HttpRequest.newBuilder() +// .uri(uri) +// .timeout(Duration.ofSeconds(connectTimeoutSeconds)) +// .header("Accept", "application/json") +// .GET() +// .build(); +// } +// +// private HttpResponse sendHttpRequest(HttpRequest request) throws Exception { +// HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); +// +// int statusCode = response.statusCode(); +// if (statusCode != 200) { +// String errorMsg = String.format( +// "HERE Maps API请求失败:状态码=%d, 响应体=%s", +// statusCode, response.body() +// ); +// log.error(errorMsg); +// throw new Exception(errorMsg); +// } +// +// return response; +// } +// +//} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapsGeocoderUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapsGeocoderUtil.java new file mode 100644 index 0000000..848635d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/hereMap/HereMapsGeocoderUtil.java @@ -0,0 +1,244 @@ +//package com.fleet.common.utils.hereMap; +// +//import com.fasterxml.jackson.databind.JsonNode; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.fleet.common.config.HereMapsProperties; +//import com.fleet.common.entity.hereMap.HereMapLocation; +//import com.google.gson.Gson; +//import com.google.gson.JsonArray; +//import com.google.gson.JsonObject; +//import lombok.Data; +//import lombok.extern.slf4j.Slf4j; +//import org.apache.http.client.methods.CloseableHttpResponse; +//import org.apache.http.client.methods.HttpGet; +//import org.apache.http.client.utils.URIBuilder; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Component; +// +//import javax.annotation.Resource; +//import java.net.URI; +//import java.net.URLEncoder; +//import java.net.http.HttpClient; +//import java.net.http.HttpRequest; +//import java.net.http.HttpResponse; +//import java.nio.charset.StandardCharsets; +//import java.time.Duration; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Optional; +// +///** +// * @author ID +// * @date 2025/9/1 15:33 +// */ +//@Component +//@Slf4j // Lombok注解:自动注入日志对象(log) +//public class HereMapsGeocoderUtil { +// +// @Resource +// private HereMapsProperties hereMapsProperties; +// +// @Resource +// private HttpClient httpClient; +// +// @Resource +// private ObjectMapper objectMapper; +// +// @Value("${http.client.connect-timeout-seconds:10}") +// private Integer connectTimeoutSeconds; +// +// /** +// * 根据地点名称查询坐标 +// * +// * @param location 地点名称(如"北京天安门") +// * @return 地点信息+坐标列表 +// * @throws Exception 包含网络异常、API错误等 +// */ +// public List getCoordinates(String location) throws Exception { +// // 1. 参数校验 +// if (location == null || location.trim().isEmpty()) { +// log.error("地点名称不能为空"); +// throw new IllegalArgumentException("地点名称不能为空"); +// } +// +// // 2. 构建请求URL +// URI requestUri = buildGeocodingUri(location.trim()); +// log.info("HERE Maps 地理编码请求URL:{}", requestUri); +// +// // 3. 构建并发送HTTP请求 +// HttpRequest request = buildHttpRequest(requestUri); +// HttpResponse response = sendHttpRequest(request); +// +// // 4. 处理响应并返回结果 +// log.info("HERE Maps API请求成功,响应体长度:{} 字符", response.body().length()); +// return parseGeocodingResponse(response.body()); +// } +// +// /** +// * 根据经纬度获取地理位置信息 +// * +// * @param latitude 纬度(-90到90之间) +// * @param longitude 经度(-180到180之间) +// * @return 地理位置信息Optional +// */ +// public HereMapLocation getLocationFromCoordinates(double latitude, double longitude) { +// try { +// // 1. 参数校验 +// validateCoordinates(latitude, longitude); +// +// // 2. 构建请求URL +// URI requestUri = buildReverseGeocodingUri(latitude, longitude); +// log.info("HERE Maps 逆地理编码请求URL:{}", requestUri); +// +// // 3. 构建并发送HTTP请求 +// HttpRequest request = buildHttpRequest(requestUri); +// HttpResponse response = sendHttpRequest(request); +// +// // 4. 处理响应并返回结果 +// log.info("HERE Maps API请求成功,响应体长度:{} 字符", response.body().length()); +// return parseLocationResponse(response.body(), latitude, longitude); +// } catch (Exception e) { +// log.error("获取地理位置信息失败", e); +// return null; +// } +// } +// +// /** +// * 构建地理编码请求URI +// */ +// private URI buildGeocodingUri(String location) throws Exception { +// String encodedLocation = URLEncoder.encode(location, StandardCharsets.UTF_8.name()); +// +// return new URIBuilder(hereMapsProperties.getGeocodingApiUrl()) +// .addParameter("q", encodedLocation) +// .addParameter("apiKey", hereMapsProperties.getApiKey()) +// .build(); +// } +// +// /** +// * 构建逆地理编码请求URI +// */ +// private URI buildReverseGeocodingUri(double latitude, double longitude) throws Exception { +// return new URIBuilder(hereMapsProperties.getReverseGeocodingApiUrl()) +// .addParameter("at", String.format("%s,%s", latitude, longitude)) +// .addParameter("apiKey", hereMapsProperties.getApiKey()) +// .build(); +// } +// +// /** +// * 构建HTTP请求 +// */ +// private HttpRequest buildHttpRequest(URI uri) { +// return HttpRequest.newBuilder() +// .uri(uri) +// .timeout(Duration.ofSeconds(connectTimeoutSeconds)) +// .header("Accept", "application/json") +// .GET() +// .build(); +// } +// +// /** +// * 发送HTTP请求并处理响应状态 +// */ +// private HttpResponse sendHttpRequest(HttpRequest request) throws Exception { +// HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); +// +// int statusCode = response.statusCode(); +// if (statusCode != 200) { +// String errorMsg = String.format( +// "HERE Maps API请求失败:状态码=%d, 响应体=%s", +// statusCode, response.body() +// ); +// log.error(errorMsg); +// throw new Exception(errorMsg); +// } +// +// return response; +// } +// +// /** +// * 解析地理编码响应 +// */ +// private List parseGeocodingResponse(String responseBody) { +// List results = new ArrayList<>(); +// try { +// JsonNode rootNode = objectMapper.readTree(responseBody); +// JsonNode itemsNode = rootNode.get("items"); +// +// if (itemsNode == null || !itemsNode.isArray() || itemsNode.size() == 0) { +// log.warn("地理编码无匹配结果"); +// return results; +// } +// +// for (JsonNode item : itemsNode) { +// String title = item.get("title").asText(); +// String address = item.get("address").get("label").asText(); +// +// JsonNode position = item.get("position"); +// double latitude = position.get("lat").asDouble(); +// double longitude = position.get("lng").asDouble(); +// +// results.add(new HereMapLocation(title, address, latitude, longitude)); +// } +// } catch (Exception e) { +// log.error("解析地理编码响应失败", e); +// throw new RuntimeException("解析地理编码结果失败", e); +// } +// return results; +// } +// +// /** +// * 解析逆地理编码响应 +// */ +// private HereMapLocation parseLocationResponse(String responseBody, double latitude, double longitude) { +// try { +// JsonNode rootNode = objectMapper.readTree(responseBody); +// JsonNode itemsNode = rootNode.get("items"); +// +// if (itemsNode != null && itemsNode.isArray() && itemsNode.size() > 0) { +// JsonNode firstItem = itemsNode.get(0); +// JsonNode addressNode = firstItem.get("address"); +// +// HereMapLocation location = new HereMapLocation(); +// location.setTitle(firstItem.get("title").asText()); +// location.setLatitude(latitude); +// location.setLongitude(longitude); +// location.setAddress(addressNode.get("label").asText()); +// location.setCity(getOptionalNodeText(addressNode, "city")); +// location.setCountry(getOptionalNodeText(addressNode, "countryName")); +// location.setPostalCode(getOptionalNodeText(addressNode, "postalCode")); +// +// return location; +// } +// +// log.warn("逆地理编码无匹配结果"); +// return null; +// } catch (Exception e) { +// log.error("解析逆地理编码响应失败", e); +// throw new RuntimeException("解析地理位置结果失败", e); +// } +// } +// +// /** +// * 校验经纬度合法性 +// */ +// private void validateCoordinates(double latitude, double longitude) { +// if (latitude < -90 || latitude > 90) { +// log.error("纬度值不合法: {}", latitude); +// throw new IllegalArgumentException("纬度必须在-90到90之间"); +// } +// if (longitude < -180 || longitude > 180) { +// log.error("经度值不合法: {}", longitude); +// throw new IllegalArgumentException("经度必须在-180到180之间"); +// } +// } +// +// /** +// * 安全获取JSON节点文本,避免空指针 +// */ +// private String getOptionalNodeText(JsonNode parentNode, String fieldName) { +// JsonNode node = parentNode.get(fieldName); +// return (node != null && !node.isNull()) ? node.asText() : ""; +// } +// +//} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/html/EscapeUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..8fa36ae --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.vetti.common.utils.html; + +import com.vetti.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author ruoyi + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + String html = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/html/HTMLFilter.java b/vetti-common/src/main/java/com/vetti/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..f255b8b --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.vetti.common.utils.html; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author ruoyi + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (!inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/utils/http/HttpHelper.java b/vetti-common/src/main/java/com/vetti/common/utils/http/HttpHelper.java new file mode 100644 index 0000000..904871c --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.vetti.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author ruoyi + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/http/HttpUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..f3dcf14 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/http/HttpUtils.java @@ -0,0 +1,293 @@ +package com.vetti.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.vetti.common.constant.Constants; +import com.vetti.common.utils.StringUtils; +import org.springframework.http.MediaType; + +/** + * 通用http发送方法 + * + * @author ruoyi + */ +public class HttpUtils +{ + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) + { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) + { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try + { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (in != null) + { + in.close(); + } + } + catch (Exception ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) + { + return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数 + * @param contentType 内容类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param, String contentType) + { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", contentType); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } + catch (IOException ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) + { + return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + + public static String sendSSLPost(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try + { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", contentType); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) + { + if (ret != null && !"".equals(ret.trim())) + { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager + { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[] {}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier + { + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/utils/ip/AddressUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/ip/AddressUtils.java new file mode 100644 index 0000000..68019d8 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.vetti.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.Constants; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @author ruoyi + */ +public class AddressUtils +{ + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // IP地址查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) + { + // 内网不查询 + if (IpUtils.internalIp(ip)) + { + return "内网IP"; + } + if (RuoYiConfig.isAddressEnabled()) + { + try + { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); + if (StringUtils.isEmpty(rspStr)) + { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/ip/IpUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/ip/IpUtils.java new file mode 100644 index 0000000..80201f5 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/ip/IpUtils.java @@ -0,0 +1,382 @@ +package com.vetti.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.servlet.http.HttpServletRequest; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils +{ + public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; + // 匹配 ip + public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")"; + public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))"; + // 匹配网段 + public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")"; + + /** + * 获取客户端IP + * + * @return IP地址 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return StringUtils.substring(ip, 0, 255); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } + + /** + * 是否为IP + */ + public static boolean isIP(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); + } + + /** + * 是否为IP,或 *为间隔的通配符地址 + */ + public static boolean isIpWildCard(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD); + } + + /** + * 检测参数是否在ip通配符里 + */ + public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) + { + String[] s1 = ipWildCard.split("\\."); + String[] s2 = ip.split("\\."); + boolean isMatchedSeg = true; + for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) + { + if (!s1[i].equals(s2[i])) + { + isMatchedSeg = false; + break; + } + } + return isMatchedSeg; + } + + /** + * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串 + */ + public static boolean isIPSegment(String ipSeg) + { + return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG); + } + + /** + * 判断ip是否在指定网段中 + */ + public static boolean ipIsInNetNoCheck(String iparea, String ip) + { + int idx = iparea.indexOf('-'); + String[] sips = iparea.substring(0, idx).split("\\."); + String[] sipe = iparea.substring(idx + 1).split("\\."); + String[] sipt = ip.split("\\."); + long ips = 0L, ipe = 0L, ipt = 0L; + for (int i = 0; i < 4; ++i) + { + ips = ips << 8 | Integer.parseInt(sips[i]); + ipe = ipe << 8 | Integer.parseInt(sipe[i]); + ipt = ipt << 8 | Integer.parseInt(sipt[i]); + } + if (ips > ipe) + { + long t = ips; + ips = ipe; + ipe = t; + } + return ips <= ipt && ipt <= ipe; + } + + /** + * 校验ip是否符合过滤串规则 + * + * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` + * @param ip 校验IP地址 + * @return boolean 结果 + */ + public static boolean isMatchedIp(String filter, String ip) + { + if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) + { + return false; + } + String[] ips = filter.split(";"); + for (String iStr : ips) + { + if (isIP(iStr) && iStr.equals(ip)) + { + return true; + } + else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) + { + return true; + } + else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelHandlerAdapter.java b/vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..97c5ed7 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,24 @@ +package com.vetti.common.utils.poi; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * Excel数据格式处理适配器 + * + * @author ruoyi + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * @param cell 单元格对象 + * @param wb 工作簿对象 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args, Cell cell, Workbook wb); +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..4370335 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1893 @@ +package com.vetti.common.utils.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.annotation.Excel.Type; +import com.vetti.common.annotation.Excels; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.text.Convert; +import com.vetti.common.exception.UtilException; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.DictUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.file.FileTypeUtils; +import com.vetti.common.utils.file.FileUtils; +import com.vetti.common.utils.file.ImageUtils; +import com.vetti.common.utils.reflect.ReflectUtils; + +/** + * Excel相关处理 + * + * @author ruoyi + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String SEPARATOR = ","; + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 用于dictType属性数据存储,避免重复查缓存 + */ + public Map sysDictMap = new HashMap(); + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Method subMethod; + + /** + * 对象的子列表属性 + */ + private List subFields; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 实体对象 + */ + public Class clazz; + + /** + * 需要显示列属性 + */ + public String[] includeFields; + + /** + * 需要排除列属性 + */ + public String[] excludeFields; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + /** + * 仅在Excel中显示列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void showColumn(String... fields) + { + this.includeFields = fields; + } + + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + titleLastCol = titleLastCol + subFields.size() - 1; + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + Row subRow = sheet.createRow(rownum); + int column = 0; + int subFieldSize = subFields != null ? subFields.size() : 0; + for (Object[] objects : fields) + { + Field field = (Field) objects[0]; + Excel attr = (Excel) objects[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + Cell cell = subRow.createCell(column); + cell.setCellValue(attr.name()); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (subFieldSize > 1) + { + CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); + sheet.addMergedRegion(cellAddress); + } + column += subFieldSize; + } + else + { + Cell cell = subRow.createCell(column++); + cell.setCellValue(attr.name()); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + } + } + rownum++; + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) + { + return importExcel(is, 0); + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) + { + List list = null; + try + { + list = importExcel(StringUtils.EMPTY, is, titleNum); + } + catch (Exception e) + { + log.error("导入Excel异常{}", e.getMessage()); + throw new UtilException(e.getMessage()); + } + finally + { + IOUtils.closeQuietly(is); + } + return list; + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map> pictures = null; + if (isXSSFWorkbook) + { + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); + } + else + { + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); + } + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + else + { + cellMap.put(null, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (s.matches("^\\d+\\.0$")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + if (!sysDictMap.containsKey(attr.dictType() + val)) + { + String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + sysDictMap.put(attr.dictType() + val, dictValue); + } + val = sysDictMap.get(attr.dictType() + val); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr, null); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + StringBuilder propertyString = new StringBuilder(); + List images = pictures.get(row.getRowNum() + "_" + entry.getKey()); + for (PictureData picture : images) + { + byte[] data = picture.getData(); + String fileName = FileUtils.writeImportBytes(data); + propertyString.append(fileName).append(SEPARATOR); + } + val = StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName) + { + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public AjaxResult exportExcel() + { + OutputStream out = null; + try + { + writeSheet(); + String filename = encodingFilename(sheetName); + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } + } + else + { + this.createHeadCell(excel, row, column++); + } + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + @SuppressWarnings("unchecked") + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + int currentRowNum = rownum + 1; // 从标题行后开始 + + for (int i = startNo; i < endNo; i++) + { + row = sheet.createRow(currentRowNum); + T vo = (T) list.get(i); + int column = 0; + int maxSubListSize = getCurrentMaxSubListSize(vo); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, excel); + if (subList != null && !subList.isEmpty()) + { + int subIndex = 0; + for (Object subVo : subList) + { + Row subRow = sheet.getRow(currentRowNum + subIndex); + if (subRow == null) + { + subRow = sheet.createRow(currentRowNum + subIndex); + } + + int subColumn = column; + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + addCell(subExcel, subRow, (T) subVo, subField, subColumn++); + } + subIndex++; + } + column += subFields.size(); + } + } + catch (Exception e) + { + log.error("填充集合数据失败", e); + } + } + else + { + // 创建单元格并设置值 + addCell(excel, row, vo, field, column); + if (maxSubListSize > 1 && excel.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column)); + } + column++; + } + } + currentRowNum += maxSubListSize; + } + } + + /** + * 获取子列表最大数 + */ + private int getCurrentMaxSubListSize(T vo) + { + int maxSubListSize = 1; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, (Excel) os[1]); + if (subList != null && !subList.isEmpty()) + { + maxSubListSize = Math.max(maxSubListSize, subList.size()); + } + } + catch (Exception e) + { + log.error("获取集合大小失败", e); + } + } + } + return maxSubListSize; + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setDataFormat(dataFormat.getFormat("######0.00")); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationHeaderStyles(wb, styles)); + + styles.putAll(annotationDataStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + // 设置表格头单元格文本形式 + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + annotationDataStyles(styles, subField, subExcel); + } + } + else + { + annotationDataStyles(styles, field, excel); + } + } + return styles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param styles 自定义样式列表 + * @param field 属性列信息 + * @param excel 注解信息 + */ + public void annotationDataStyles(Map styles, Field field, Excel excel) + { + String key = StringUtils.format("data_{}_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType(), excel.wrapText()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + style.setWrapText(excel.wrapText()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + if (ColumnType.TEXT == excel.cellType()) + { + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + } + styles.put(key, style); + } + } + + /** + * 创建单元格 + */ + public Cell createHeadCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String propertyValue = Convert.toStr(value); + if (StringUtils.isNotEmpty(propertyValue)) + { + List imagePaths = StringUtils.str2List(propertyValue, SEPARATOR); + for (String imagePath : imagePaths) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) + { + String[] comboArray = attr.combo(); + if (attr.comboReadDict()) + { + if (!sysDictMap.containsKey("combo_" + attr.dictType())) + { + String labels = DictUtils.getDictLabels(attr.dictType()); + sysDictMap.put("combo_" + attr.dictType(), labels); + } + String val = sysDictMap.get("combo_" + attr.dictType()); + comboArray = StringUtils.split(val, DictUtils.SEPARATOR); + } + if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + } + } + + /** + * 添加单元格 + */ + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + { + if (subMergedLastRowNum >= subMergedFirstRowNum) + { + sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); + } + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) + { + if (!sysDictMap.containsKey(dictType + value)) + { + String lable = convertDictByExp(Convert.toStr(value), dictType, separator); + sysDictMap.put(dictType + value, lable); + } + cell.setCellValue(sysDictMap.get(dictType + value)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) + { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + Name name = wb.createName(); + name.setNameName(hideSheetName + "_data"); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(SEPARATOR); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(SEPARATOR); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 解析字典值 + * + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String convertDictByExp(String dictValue, String dictType, String separator) + { + return DictUtils.getDictLabel(dictType, dictValue, separator); + } + + /** + * 反向解析值字典值 + * + * @param dictLabel 字典标签 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典值 + */ + public static String reverseDictByExp(String dictLabel, String dictType, String separator) + { + return DictUtils.getDictValue(dictType, dictLabel, separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) + { + try + { + Object instance = excel.handler().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); + value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(statistics.get(key)); + } + statistics.clear(); + } + } + + /** + * 编码文件名 + */ + public String encodingFilename(String filename) + { + return UUID.randomUUID() + "_" + filename + ".xlsx"; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = RuoYiConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + field.setAccessible(true); + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + if (StringUtils.isNotEmpty(includeFields)) + { + for (Field field : tempFields) + { + if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class)) + { + addField(fields, field); + } + } + } + else if (StringUtils.isNotEmpty(excludeFields)) + { + for (Field field : tempFields) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) + { + addField(fields, field); + } + } + } + else + { + for (Field field : tempFields) + { + addField(fields, field); + } + } + return fields; + } + + /** + * 添加字段信息 + */ + public void addField(List fields, Field field) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (StringUtils.isNotEmpty(includeFields)) + { + if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + else + { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + } + } + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + wb.setSheetName(index, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map> sheetIndexPicMap = new HashMap<>(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty() && sheet.getDrawingPatriarch() != null) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + HSSFClientAnchor anchor = (HSSFClientAnchor) pic.getAnchor(); + String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); + sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + } + } + } + return sheetIndexPicMap; + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map> sheetIndexPicMap = new HashMap<>(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + for (XSSFShape shape : drawing.getShapes()) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0; + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + } + + /** + * 获取集合的值 + */ + public Collection getListCellValue(Object obj) + { + Object value; + try + { + value = subMethod.invoke(obj, new Object[] {}); + } + catch (Exception e) + { + return new ArrayList(); + } + return (Collection) value; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/reflect/ReflectUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..7da9e6d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.vetti.common.utils.reflect; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Date; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.vetti.common.core.text.Convert; +import com.vetti.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author ruoyi + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/sign/Base64.java b/vetti-common/src/main/java/com/vetti/common/utils/sign/Base64.java new file mode 100644 index 0000000..253b6e2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.vetti.common.utils.sign; + +/** + * Base64工具类 + * + * @author ruoyi + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/sign/Md5Utils.java b/vetti-common/src/main/java/com/vetti/common/utils/sign/Md5Utils.java new file mode 100644 index 0000000..fd4b824 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/sign/Md5Utils.java @@ -0,0 +1,96 @@ +package com.vetti.common.utils.sign; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Md5加密方法 + * + * @author ruoyi + */ +public class Md5Utils +{ + private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + + private static byte[] md5(String s) + { + MessageDigest algorithm; + try + { + algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(s.getBytes("UTF-8")); + byte[] messageDigest = algorithm.digest(); + return messageDigest; + } + catch (Exception e) + { + log.error("MD5 Error...", e); + } + return null; + } + + private static final String toHex(byte hash[]) + { + if (hash == null) + { + return null; + } + StringBuffer buf = new StringBuffer(hash.length * 2); + int i; + + for (i = 0; i < hash.length; i++) + { + if ((hash[i] & 0xff) < 0x10) + { + buf.append("0"); + } + buf.append(Long.toString(hash[i] & 0xff, 16)); + } + return buf.toString(); + } + + public static String hash(String s) + { + try + { + return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } + catch (Exception e) + { + log.error("not supported charset...{}", e); + return s; + } + } + + /** + * 给文件MD5加密 + * @param file + * @return + */ + public static String getFileMd5(File file) { + if (file == null || !file.exists() || !file.isFile()) return null; + FileInputStream in = null; + try { + in = new FileInputStream(file); + return DigestUtils.md5Hex(in); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/spring/SpringUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/spring/SpringUtils.java new file mode 100644 index 0000000..881b9e2 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/spring/SpringUtils.java @@ -0,0 +1,164 @@ +package com.vetti.common.utils.spring; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import com.vetti.common.utils.StringUtils; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + Object proxy = AopContext.currentProxy(); + if (((Advised) proxy).getTargetSource().getTargetClass() == invoker.getClass()) + { + return (T) proxy; + } + return invoker; + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/sql/SqlUtil.java b/vetti-common/src/main/java/com/vetti/common/utils/sql/SqlUtil.java new file mode 100644 index 0000000..b7fa37d --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/sql/SqlUtil.java @@ -0,0 +1,70 @@ +package com.vetti.common.utils.sql; + +import com.vetti.common.exception.UtilException; +import com.vetti.common.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil +{ + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 限制orderBy最大长度 + */ + private static final int ORDER_BY_MAX_LENGTH = 500; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) + { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) + { + throw new UtilException("参数不符合规范,不能进行查询"); + } + if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH) + { + throw new UtilException("参数已超过最大限制,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) + { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) + { + if (StringUtils.isEmpty(value)) + { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) + { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) + { + throw new UtilException("参数存在SQL注入风险"); + } + } + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/uuid/IdUtils.java b/vetti-common/src/main/java/com/vetti/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..daafe3e --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.vetti.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author ruoyi + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/uuid/Seq.java b/vetti-common/src/main/java/com/vetti/common/utils/uuid/Seq.java new file mode 100644 index 0000000..6b5fe58 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.vetti.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.StringUtils; + +/** + * @author ruoyi 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/utils/uuid/UUID.java b/vetti-common/src/main/java/com/vetti/common/utils/uuid/UUID.java new file mode 100644 index 0000000..9feffdb --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.vetti.common.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.vetti.common.exception.UtilException; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author ruoyi + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/vetti-common/src/main/java/com/vetti/common/xss/Xss.java b/vetti-common/src/main/java/com/vetti/common/xss/Xss.java new file mode 100644 index 0000000..8dcddcd --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.vetti.common.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/vetti-common/src/main/java/com/vetti/common/xss/XssValidator.java b/vetti-common/src/main/java/com/vetti/common/xss/XssValidator.java new file mode 100644 index 0000000..615d038 --- /dev/null +++ b/vetti-common/src/main/java/com/vetti/common/xss/XssValidator.java @@ -0,0 +1,39 @@ +package com.vetti.common.xss; + +import com.vetti.common.utils.StringUtils; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + StringBuilder sHtml = new StringBuilder(); + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) + { + sHtml.append(matcher.group()); + } + return pattern.matcher(sHtml).matches(); + } +} \ No newline at end of file diff --git a/vetti-framework/pom.xml b/vetti-framework/pom.xml new file mode 100644 index 0000000..6d2a3f8 --- /dev/null +++ b/vetti-framework/pom.xml @@ -0,0 +1,64 @@ + + + + vetti-service + com.vetti + 3.9.0 + + 4.0.0 + + vetti-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-starter + + + + + pro.fessional + kaptcha + + + servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.vetti + vetti-system + + + + + \ No newline at end of file diff --git a/vetti-framework/src/main/java/com/vetti/framework/aspectj/DataScopeAspect.java b/vetti-framework/src/main/java/com/vetti/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..cc8d12d --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,184 @@ +package com.vetti.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.vetti.common.annotation.DataScope; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.text.Convert; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class DataScopeAspect +{ + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable + { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) + { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) + { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) + { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList(); + List scopeCustomIds = new ArrayList(); + user.getRoles().forEach(role -> { + if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + scopeCustomIds.add(Convert.toStr(role.getRoleId())); + } + }); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) + { + continue; + } + if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + if (scopeCustomIds.size() > 1) + { + // 多个自定数据权限使用in查询,避免多次拼接。 + sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds))); + } + else + { + sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); + } + } + else if (DATA_SCOPE_DEPT.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } + else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId())); + } + else if (DATA_SCOPE_SELF.equals(dataScope)) + { + if (StringUtils.isNotBlank(userAlias)) + { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } + else + { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + conditions.add(dataScope); + } + + // 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 + if (StringUtils.isEmpty(conditions)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + + if (StringUtils.isNotBlank(sqlString.toString())) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/aspectj/DataSourceAspect.java b/vetti-framework/src/main/java/com/vetti/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..bd7374c --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +package com.vetti.framework.aspectj; + +import java.util.Objects; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import com.vetti.common.annotation.DataSource; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author ruoyi + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect +{ + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.vetti.common.annotation.DataSource)" + + "|| @within(com.vetti.common.annotation.DataSource)") + public void dsPointCut() + { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable + { + DataSource dataSource = getDataSource(point); + + if (StringUtils.isNotNull(dataSource)) + { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } + + try + { + return point.proceed(); + } + finally + { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) + { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) + { + return dataSource; + } + + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/aspectj/LogAspect.java b/vetti-framework/src/main/java/com/vetti/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..f266f06 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/aspectj/LogAspect.java @@ -0,0 +1,256 @@ +package com.vetti.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import com.alibaba.fastjson2.JSON; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.text.Convert; +import com.vetti.common.enums.BusinessStatus; +import com.vetti.common.enums.HttpMethod; +import com.vetti.common.filter.PropertyPreExcludeFilter; +import com.vetti.common.utils.ExceptionUtil; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.framework.manager.AsyncManager; +import com.vetti.framework.manager.factory.AsyncFactory; +import com.vetti.system.domain.SysOperLog; + +/** + * 操作日志记录处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class LogAspect +{ + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间 */ + private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void doBefore(JoinPoint joinPoint, Log controllerLog) + { + TIME_THREADLOCAL.set(System.currentTimeMillis()); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) + { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) + { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) + { + try + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (loginUser != null) + { + operLog.setOperName(loginUser.getUsername()); + SysUser currentUser = loginUser.getUser(); + if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept())) + { + operLog.setDeptName(currentUser.getDept().getDeptName()); + } + } + + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } + catch (Exception exp) + { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + finally + { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception + { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) + { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) + { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception + { + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) + { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + else + { + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) + { + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(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/vetti-framework/src/main/java/com/vetti/framework/aspectj/RateLimiterAspect.java b/vetti-framework/src/main/java/com/vetti/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..5e83e08 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,89 @@ +package com.vetti.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.vetti.common.annotation.RateLimiter; +import com.vetti.common.enums.LimitType; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/ApplicationConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..f3f8df4 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/ApplicationConfig.java @@ -0,0 +1,30 @@ +package com.vetti.framework.config; + +import java.util.TimeZone; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author ruoyi + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.vetti.**.mapper") +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/CaptchaConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..284d068 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.vetti.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.vetti.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/DruidConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/DruidConfig.java new file mode 100644 index 0000000..ed88c7c --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/DruidConfig.java @@ -0,0 +1,126 @@ +package com.vetti.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.vetti.common.enums.DataSourceType; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.framework.config.properties.DruidProperties; +import com.vetti.framework.datasource.DynamicDataSource; + +/** + * druid 配置多数据源 + * + * @author ruoyi + */ +@Configuration +public class DruidConfig +{ + @Bean + @ConfigurationProperties("spring.datasource.druid.master") + public DataSource masterDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") + public DataSource slaveDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean(name = "dynamicDataSource") + @Primary + public DynamicDataSource dataSource(DataSource masterDataSource) + { + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); + setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); + return new DynamicDataSource(masterDataSource, targetDataSources); + } + + /** + * 设置数据源 + * + * @param targetDataSources 备选数据源集合 + * @param sourceName 数据源名称 + * @param beanName bean名称 + */ + public void setDataSource(Map targetDataSources, String sourceName, String beanName) + { + try + { + DataSource dataSource = SpringUtils.getBean(beanName); + targetDataSources.put(sourceName, dataSource); + } + catch (Exception e) + { + } + } + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) + { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() + { + @Override + public void init(javax.servlet.FilterConfig filterConfig) throws ServletException + { + } + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + @Override + public void destroy() + { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/FastJson2JsonRedisSerializer.java b/vetti-framework/src/main/java/com/vetti/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..25e5997 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,52 @@ +package com.vetti.framework.config; + +import java.nio.charset.Charset; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import com.vetti.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/FilterConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/FilterConfig.java new file mode 100644 index 0000000..10532ca --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/FilterConfig.java @@ -0,0 +1,58 @@ +package com.vetti.framework.config; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.vetti.common.filter.RepeatableFilter; +import com.vetti.common.filter.XssFilter; +import com.vetti.common.utils.StringUtils; + +/** + * Filter配置 + * + * @author ruoyi + */ +@Configuration +public class FilterConfig +{ + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/I18nConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/I18nConfig.java new file mode 100644 index 0000000..81c561d --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/I18nConfig.java @@ -0,0 +1,43 @@ +package com.vetti.framework.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import com.vetti.common.constant.Constants; + +/** + * 资源文件配置加载 + * + * @author ruoyi + */ +@Configuration +public class I18nConfig implements WebMvcConfigurer +{ + @Bean + public LocaleResolver localeResolver() + { + SessionLocaleResolver slr = new SessionLocaleResolver(); + // 默认语言 + slr.setDefaultLocale(Constants.DEFAULT_LOCALE); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() + { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + // 参数名 + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/KaptchaTextCreator.java b/vetti-framework/src/main/java/com/vetti/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..83b7294 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/KaptchaTextCreator.java @@ -0,0 +1,68 @@ +package com.vetti.framework.config; + +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author ruoyi + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = random.nextInt(3); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/MyBatisConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..f034dba --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/MyBatisConfig.java @@ -0,0 +1,132 @@ +package com.vetti.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.sql.DataSource; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; +import com.vetti.common.utils.StringUtils; + +/** + * Mybatis支持*匹配扫描包 + * + * @author ruoyi + */ +@Configuration +public class MyBatisConfig +{ + @Autowired + private Environment env; + + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + public static String setTypeAliasesPackage(String typeAliasesPackage) + { + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + List allResult = new ArrayList(); + try + { + for (String aliasesPackage : typeAliasesPackage.split(",")) + { + List result = new ArrayList(); + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; + Resource[] resources = resolver.getResources(aliasesPackage); + if (resources != null && resources.length > 0) + { + MetadataReader metadataReader = null; + for (Resource resource : resources) + { + if (resource.isReadable()) + { + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try + { + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + } + } + if (result.size() > 0) + { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + if (allResult.size() > 0) + { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } + else + { + throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + public Resource[] resolveMapperLocations(String[] mapperLocations) + { + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + List resources = new ArrayList(); + if (mapperLocations != null) + { + for (String mapperLocation : mapperLocations) + { + try + { + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } + catch (IOException e) + { + // ignore + } + } + } + return resources.toArray(new Resource[resources.size()]); + } + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception + { + String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis.mapperLocations"); + String configLocation = env.getProperty("mybatis.configLocation"); + typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + return sessionFactory.getObject(); + } +} \ No newline at end of file diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/RedisConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/RedisConfig.java new file mode 100644 index 0000000..d366894 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/RedisConfig.java @@ -0,0 +1,69 @@ +package com.vetti.framework.config; + +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author ruoyi + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() + { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/ResourcesConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..0a3da9c --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/ResourcesConfig.java @@ -0,0 +1,72 @@ +package com.vetti.framework.config; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.vetti.common.config.RuoYiConfig; +import com.vetti.common.constant.Constants; +import com.vetti.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author ruoyi + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer +{ + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic()); + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() + { + CorsConfiguration config = new CorsConfiguration(); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/SecurityConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/SecurityConfig.java new file mode 100644 index 0000000..d92da99 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/SecurityConfig.java @@ -0,0 +1,139 @@ +package com.vetti.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +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.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.vetti.framework.config.properties.PermitAllUrlProperties; +import com.vetti.framework.security.filter.JwtAuthenticationTokenFilter; +import com.vetti.framework.security.handle.AuthenticationEntryPointImpl; +import com.vetti.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author ruoyi + */ +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Configuration +public class SecurityConfig +{ + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * 身份验证实现 + */ + @Bean + public AuthenticationManager authenticationManager() + { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setUserDetailsService(userDetailsService); + daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder()); + return new ProviderManager(daoAuthenticationProvider); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Bean + protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception + { + return httpSecurity + // CSRF禁用,因为不使用session + .csrf(csrf -> csrf.disable()) + // 禁用HTTP响应标头 + .headers((headersCustomizer) -> { + headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin()); + }) + // 认证失败处理类 + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + // 基于token,所以不需要session + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // 注解标记允许匿名访问的url + .authorizeHttpRequests((requests) -> { + permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + requests.antMatchers("/login", "/register", "/captchaImage","/v1/app/**").permitAll() + // 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated(); + }) + // 添加Logout filter + .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)) + // 添加JWT filter + .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) + // 添加CORS filter + .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) + .addFilterBefore(corsFilter, LogoutFilter.class) + .build(); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/ServerConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/ServerConfig.java new file mode 100644 index 0000000..388ae56 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.vetti.framework.config; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.vetti.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author ruoyi + */ +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/ThreadPoolConfig.java b/vetti-framework/src/main/java/com/vetti/framework/config/ThreadPoolConfig.java new file mode 100644 index 0000000..e658936 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/ThreadPoolConfig.java @@ -0,0 +1,63 @@ +package com.vetti.framework.config; + +import com.vetti.common.utils.Threads; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + * + * @author ruoyi + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小 + private int corePoolSize = 50; + + // 最大可创建的线程数 + private int maxPoolSize = 200; + + // 队列最大长度 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间 + private int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor(corePoolSize, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/properties/DruidProperties.java b/vetti-framework/src/main/java/com/vetti/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..ad91613 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/properties/DruidProperties.java @@ -0,0 +1,89 @@ +package com.vetti.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author ruoyi + */ +@Configuration +public class DruidProperties +{ + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.connectTimeout}") + private int connectTimeout; + + @Value("${spring.datasource.druid.socketTimeout}") + private int socketTimeout; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) + { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */ + datasource.setConnectTimeout(connectTimeout); + + /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */ + datasource.setSocketTimeout(socketTimeout); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/config/properties/PermitAllUrlProperties.java b/vetti-framework/src/main/java/com/vetti/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 0000000..7859a33 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,73 @@ +package com.vetti.framework.config.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import com.vetti.common.annotation.Anonymous; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author ruoyi + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware +{ + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + @Override + public void afterPropertiesSet() + { + RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); + + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException + { + this.applicationContext = context; + } + + public List getUrls() + { + return urls; + } + + public void setUrls(List urls) + { + this.urls = urls; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSource.java b/vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..a710501 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSource.java @@ -0,0 +1,26 @@ +package com.vetti.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author ruoyi + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSourceContextHolder.java b/vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..a1685b1 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.vetti.framework.datasource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author ruoyi + */ +public class DynamicDataSourceContextHolder +{ + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) + { + log.info("切换到{}数据源", dsType); + CONTEXT_HOLDER.set(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() + { + return CONTEXT_HOLDER.get(); + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() + { + CONTEXT_HOLDER.remove(); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/interceptor/RepeatSubmitInterceptor.java b/vetti-framework/src/main/java/com/vetti/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..45becf4 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,56 @@ +package com.vetti.framework.interceptor; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.JSON; +import com.vetti.common.annotation.RepeatSubmit; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author ruoyi + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + if (this.isRepeatSubmit(request, annotation)) + { + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request 请求信息 + * @param annotation 防重复注解参数 + * @return 结果 + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/interceptor/impl/SameUrlDataInterceptor.java b/vetti-framework/src/main/java/com/vetti/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..c1bb8e3 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,110 @@ +package com.vetti.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.vetti.common.annotation.RepeatSubmit; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.filter.RepeatedlyRequestWrapper; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.http.HttpHelper; +import com.vetti.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author ruoyi + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + public final String REPEAT_PARAMS = "repeatParams"; + + public final String REPEAT_TIME = "repeatTime"; + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @Autowired + private RedisCache redisCache; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; + + Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); + if (sessionObj != null) + { + Map sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/manager/AsyncManager.java b/vetti-framework/src/main/java/com/vetti/framework/manager/AsyncManager.java new file mode 100644 index 0000000..0647732 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.vetti.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.vetti.common.utils.Threads; +import com.vetti.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author ruoyi + */ +public class AsyncManager +{ + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager(){} + + private static AsyncManager me = new AsyncManager(); + + public static AsyncManager me() + { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/manager/ShutdownManager.java b/vetti-framework/src/main/java/com/vetti/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..0776f38 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/manager/ShutdownManager.java @@ -0,0 +1,39 @@ +package com.vetti.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import javax.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author ruoyi + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() + { + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/manager/factory/AsyncFactory.java b/vetti-framework/src/main/java/com/vetti/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..b895f8a --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.vetti.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.vetti.common.constant.Constants; +import com.vetti.common.utils.LogUtils; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.AddressUtils; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.system.domain.SysLogininfor; +import com.vetti.system.domain.SysOperLog; +import com.vetti.system.service.ISysLogininforService; +import com.vetti.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author ruoyi + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, + final Object... args) + { + final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = IpUtils.getIpAddr(); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/security/context/AuthenticationContextHolder.java b/vetti-framework/src/main/java/com/vetti/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 0000000..0bb0937 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.vetti.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author ruoyi + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/security/context/PermissionContextHolder.java b/vetti-framework/src/main/java/com/vetti/framework/security/context/PermissionContextHolder.java new file mode 100644 index 0000000..8ee9852 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.vetti.framework.security.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.vetti.common.core.text.Convert; + +/** + * 权限信息 + * + * @author ruoyi + */ +public class PermissionContextHolder +{ + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + public static void setContext(String permission) + { + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + public static String getContext() + { + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/security/filter/JwtAuthenticationTokenFilter.java b/vetti-framework/src/main/java/com/vetti/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..ab6edf4 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.vetti.framework.security.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author ruoyi + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + chain.doFilter(request, response); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java b/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..341b496 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.vetti.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.vetti.common.constant.HttpStatus; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author ruoyi + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/security/handle/LogoutSuccessHandlerImpl.java b/vetti-framework/src/main/java/com/vetti/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..573f482 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,53 @@ +package com.vetti.framework.security.handle; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.alibaba.fastjson2.JSON; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.manager.AsyncManager; +import com.vetti.framework.manager.factory.AsyncFactory; +import com.vetti.framework.web.service.TokenService; + +/** + * 自定义退出处理类 返回成功 + * + * @author ruoyi + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler +{ + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/domain/Server.java b/vetti-framework/src/main/java/com/vetti/framework/web/domain/Server.java new file mode 100644 index 0000000..b2e0d4a --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.vetti.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.vetti.common.utils.Arith; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.framework.web.domain.server.Cpu; +import com.vetti.framework.web.domain.server.Jvm; +import com.vetti.framework.web.domain.server.Mem; +import com.vetti.framework.web.domain.server.Sys; +import com.vetti.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author ruoyi + */ +public class Server +{ + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() + { + return cpu; + } + + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + public Mem getMem() + { + return mem; + } + + public void setMem(Mem mem) + { + this.mem = mem; + } + + public Jvm getJvm() + { + return jvm; + } + + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + public Sys getSys() + { + return sys; + } + + public void setSys(Sys sys) + { + this.sys = sys; + } + + public List getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) + { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) + { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } + else + { + return String.format("%d B", size); + } + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Cpu.java b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..44d6757 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.vetti.framework.web.domain.server; + +import com.vetti.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author ruoyi + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() + { + return cpuNum; + } + + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) + { + this.sys = sys; + } + + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) + { + this.used = used; + } + + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) + { + this.wait = wait; + } + + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) + { + this.free = free; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Jvm.java b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..76058fc --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.vetti.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.vetti.common.utils.Arith; +import com.vetti.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author ruoyi + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) + { + this.max = max; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) + { + this.free = free; + } + + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getHome() + { + return home; + } + + public void setHome(String home) + { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * JDK运行时间 + */ + public String getRunTime() + { + return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Mem.java b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..2303488 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.vetti.framework.web.domain.server; + +import com.vetti.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author ruoyi + */ +public class Mem +{ + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) + { + this.total = total; + } + + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) + { + this.used = used; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) + { + this.free = free; + } + + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Sys.java b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..b8b28c3 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.vetti.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author ruoyi + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() + { + return computerName; + } + + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + public String getComputerIp() + { + return computerIp; + } + + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + public String getUserDir() + { + return userDir; + } + + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + public String getOsName() + { + return osName; + } + + public void setOsName(String osName) + { + this.osName = osName; + } + + public String getOsArch() + { + return osArch; + } + + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/SysFile.java b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..ab5244f --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.vetti.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author ruoyi + */ +public class SysFile +{ + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() + { + return dirName; + } + + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + public String getSysTypeName() + { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() + { + return typeName; + } + + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + public String getTotal() + { + return total; + } + + public void setTotal(String total) + { + this.total = total; + } + + public String getFree() + { + return free; + } + + public void setFree(String free) + { + this.free = free; + } + + public String getUsed() + { + return used; + } + + public void setUsed(String used) + { + this.used = used; + } + + public double getUsage() + { + return usage; + } + + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/exception/GlobalExceptionHandler.java b/vetti-framework/src/main/java/com/vetti/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..a845e25 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,145 @@ +package com.vetti.framework.web.exception; + +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import com.vetti.common.constant.HttpStatus; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.text.Convert; +import com.vetti.common.exception.DemoModeException; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.html.EscapeUtil; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + String value = Convert.toStr(e.getValue()); + if (StringUtils.isNotEmpty(value)) + { + value = EscapeUtil.clean(value); + } + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value)); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/PermissionService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/PermissionService.java new file mode 100644 index 0000000..4733517 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/PermissionService.java @@ -0,0 +1,159 @@ +package com.vetti.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.security.context.PermissionContextHolder; + +/** + * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author ruoyi + */ +@Service("ss") +public class PermissionService +{ + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) + { + if (StringUtils.isEmpty(permission)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) + { + if (StringUtils.isEmpty(permissions)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permissions); + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)) + { + if (permission != null && hasPermissions(authorities, permission)) + { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + if (StringUtils.isEmpty(role)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) + { + String roleKey = sysRole.getRoleKey(); + if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) + { + return true; + } + } + return false; + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) + { + return hasRole(role) != true; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) + { + if (StringUtils.isEmpty(roles)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (String role : roles.split(Constants.ROLE_DELIMETER)) + { + if (hasRole(role)) + { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) + { + return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java new file mode 100644 index 0000000..e7788b6 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysLoginService.java @@ -0,0 +1,181 @@ +package com.vetti.framework.web.service; + +import javax.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.exception.user.BlackListException; +import com.vetti.common.exception.user.CaptchaException; +import com.vetti.common.exception.user.CaptchaExpireException; +import com.vetti.common.exception.user.UserNotExistsException; +import com.vetti.common.exception.user.UserPasswordNotMatchException; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.framework.manager.AsyncManager; +import com.vetti.framework.manager.factory.AsyncFactory; +import com.vetti.framework.security.context.AuthenticationContextHolder; +import com.vetti.system.service.ISysConfigService; +import com.vetti.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + redisCache.deleteObject(verifyKey); + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // IP黑名单校验 + String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr()); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/SysPasswordService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysPasswordService.java new file mode 100644 index 0000000..d220354 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysPasswordService.java @@ -0,0 +1,86 @@ +package com.vetti.framework.web.service; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.exception.user.UserPasswordNotMatchException; +import com.vetti.common.exception.user.UserPasswordRetryLimitExceedException; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.framework.security.context.AuthenticationContextHolder; + +/** + * 登录密码方法 + * + * @author ruoyi + */ +@Component +public class SysPasswordService +{ + @Autowired + private RedisCache redisCache; + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * @return 缓存键key + */ + private String getCacheKey(String username) + { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + if (retryCount == null) + { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + if (!matches(user, password)) + { + retryCount = retryCount + 1; + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + if (redisCache.hasKey(getCacheKey(loginName))) + { + redisCache.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/SysPermissionService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysPermissionService.java new file mode 100644 index 0000000..1083eb3 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysPermissionService.java @@ -0,0 +1,88 @@ +package com.vetti.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.service.ISysMenuService; +import com.vetti.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author ruoyi + */ +@Component +public class SysPermissionService +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) + { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + roles.add("admin"); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) + { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + perms.add("*:*:*"); + } + else + { + List roles = user.getRoles(); + if (!CollectionUtils.isEmpty(roles)) + { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) + { + if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && !role.isAdmin()) + { + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/SysRegisterService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysRegisterService.java new file mode 100644 index 0000000..18358fd --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/SysRegisterService.java @@ -0,0 +1,117 @@ +package com.vetti.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.RegisterBody; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.exception.user.CaptchaException; +import com.vetti.common.exception.user.CaptchaExpireException; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.framework.manager.AsyncManager; +import com.vetti.framework.manager.factory.AsyncFactory; +import com.vetti.system.service.ISysConfigService; +import com.vetti.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author ruoyi + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private RedisCache redisCache; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPwdUpdateDate(DateUtils.getNowDate()); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/TokenService.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/TokenService.java new file mode 100644 index 0000000..d8cf9dd --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/TokenService.java @@ -0,0 +1,232 @@ +package com.vetti.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.utils.ServletUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.ip.AddressUtils; +import com.vetti.common.utils.ip.IpUtils; +import com.vetti.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +public class TokenService +{ + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L; + + @Autowired + private RedisCache redisCache; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + claims.put(Constants.JWT_USERNAME, loginUser.getUsername()); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser 登录信息 + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) + { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) + { + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) + { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) + { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) + { + String token = request.getHeader(header); + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) + { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } + + private String getTokenKey(String uuid) + { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } +} diff --git a/vetti-framework/src/main/java/com/vetti/framework/web/service/UserDetailsServiceImpl.java b/vetti-framework/src/main/java/com/vetti/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..7661e86 --- /dev/null +++ b/vetti-framework/src/main/java/com/vetti/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,66 @@ +package com.vetti.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.enums.UserStatus; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.MessageUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author ruoyi + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException + { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/vetti-generator/pom.xml b/vetti-generator/pom.xml new file mode 100644 index 0000000..495e9a9 --- /dev/null +++ b/vetti-generator/pom.xml @@ -0,0 +1,40 @@ + + + + vetti-service + com.vetti + 3.9.0 + + 4.0.0 + + vetti-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + com.vetti + vetti-common + + + + + com.alibaba + druid-spring-boot-starter + + + + + \ No newline at end of file diff --git a/vetti-generator/src/main/java/com/vetti/generator/config/GenConfig.java b/vetti-generator/src/main/java/com/vetti/generator/config/GenConfig.java new file mode 100644 index 0000000..0054003 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/config/GenConfig.java @@ -0,0 +1,87 @@ +package com.vetti.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * 读取代码生成相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "gen") +@PropertySource(value = { "classpath:generator.yml" }) +public class GenConfig +{ + /** 作者 */ + public static String author; + + /** 生成包路径 */ + public static String packageName; + + /** 自动去除表前缀 */ + public static boolean autoRemovePre; + + /** 表前缀 */ + public static String tablePrefix; + + /** 是否允许生成文件覆盖到本地(自定义路径) */ + public static boolean allowOverwrite; + + public static String getAuthor() + { + return author; + } + + @Value("${author}") + public void setAuthor(String author) + { + GenConfig.author = author; + } + + public static String getPackageName() + { + return packageName; + } + + @Value("${packageName}") + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() + { + return tablePrefix; + } + + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } + + public static boolean isAllowOverwrite() + { + return allowOverwrite; + } + + @Value("${allowOverwrite}") + public void setAllowOverwrite(boolean allowOverwrite) + { + GenConfig.allowOverwrite = allowOverwrite; + } +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/controller/GenController.java b/vetti-generator/src/main/java/com/vetti/generator/controller/GenController.java new file mode 100644 index 0000000..f52f199 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/controller/GenController.java @@ -0,0 +1,263 @@ +package com.vetti.generator.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +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 com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.core.text.Convert; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.sql.SqlUtil; +import com.vetti.generator.config.GenConfig; +import com.vetti.generator.domain.GenTable; +import com.vetti.generator.domain.GenTableColumn; +import com.vetti.generator.service.IGenTableColumnService; +import com.vetti.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + + /** + * 查询代码生成列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) + { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 获取代码生成信息 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable Long tableId) + { + GenTable table = genTableService.selectGenTableById(tableId); + List tables = genTableService.selectGenTableAll(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return success(map); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(Long tableId) + { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(String tables) + { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList, SecurityUtils.getUsername()); + return success(); + } + + /** + * 创建表结构(保存) + */ + @PreAuthorize("@ss.hasRole('admin')") + @Log(title = "创建表", businessType = BusinessType.OTHER) + @PostMapping("/createTable") + public AjaxResult createTableSave(String sql) + { + try + { + SqlUtil.filterKeyword(sql); + List sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql); + List tableNames = new ArrayList<>(); + for (SQLStatement sqlStatement : sqlStatements) + { + if (sqlStatement instanceof MySqlCreateTableStatement) + { + MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement; + if (genTableService.createTable(createTableStatement.toString())) + { + String tableName = createTableStatement.getTableName().replaceAll("`", ""); + tableNames.add(tableName); + } + } + } + List tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); + String operName = SecurityUtils.getUsername(); + genTableService.importGenTable(tableList, operName); + return AjaxResult.success(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + return AjaxResult.error("创建表结构异常"); + } + } + + /** + * 修改保存代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTable genTable) + { + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + return success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException + { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) + { + if (!GenConfig.isAllowOverwrite()) + { + return AjaxResult.error("【系统预设】不允许生成文件覆盖到本地"); + } + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) + { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + public void batchGenCode(HttpServletResponse response, String tables) throws IOException + { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + /** + * 生成zip文件 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException + { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} \ No newline at end of file diff --git a/vetti-generator/src/main/java/com/vetti/generator/domain/GenTable.java b/vetti-generator/src/main/java/com/vetti/generator/domain/GenTable.java new file mode 100644 index 0000000..df6e59e --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/domain/GenTable.java @@ -0,0 +1,385 @@ +package com.vetti.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import org.apache.commons.lang3.ArrayUtils; +import com.vetti.common.constant.GenConstants; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.utils.StringUtils; + +/** + * 业务表 gen_table + * + * @author ruoyi + */ +public class GenTable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + private String subTableName; + + /** 本表关联父表的外键名 */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + private String tplCategory; + + /** 前端类型(element-ui模版 element-plus模版) */ + private String tplWebType; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenTableColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List columns; + + /** 其它生成选项 */ + private String options; + + /** 树编码字段 */ + private String treeCode; + + /** 树父编码字段 */ + private String treeParentCode; + + /** 树名称字段 */ + private String treeName; + + /** 上级菜单ID字段 */ + private Long parentMenuId; + + /** 上级菜单名称字段 */ + private String parentMenuName; + + public Long getTableId() + { + return tableId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public String getTableName() + { + return tableName; + } + + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + public String getTableComment() + { + return tableComment; + } + + public void setTableComment(String tableComment) + { + this.tableComment = tableComment; + } + + public String getSubTableName() + { + return subTableName; + } + + public void setSubTableName(String subTableName) + { + this.subTableName = subTableName; + } + + public String getSubTableFkName() + { + return subTableFkName; + } + + public void setSubTableFkName(String subTableFkName) + { + this.subTableFkName = subTableFkName; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public String getTplCategory() + { + return tplCategory; + } + + public void setTplCategory(String tplCategory) + { + this.tplCategory = tplCategory; + } + + public String getTplWebType() + { + return tplWebType; + } + + public void setTplWebType(String tplWebType) + { + this.tplWebType = tplWebType; + } + + public String getPackageName() + { + return packageName; + } + + public void setPackageName(String packageName) + { + this.packageName = packageName; + } + + public String getModuleName() + { + return moduleName; + } + + public void setModuleName(String moduleName) + { + this.moduleName = moduleName; + } + + public String getBusinessName() + { + return businessName; + } + + public void setBusinessName(String businessName) + { + this.businessName = businessName; + } + + public String getFunctionName() + { + return functionName; + } + + public void setFunctionName(String functionName) + { + this.functionName = functionName; + } + + public String getFunctionAuthor() + { + return functionAuthor; + } + + public void setFunctionAuthor(String functionAuthor) + { + this.functionAuthor = functionAuthor; + } + + public String getGenType() + { + return genType; + } + + public void setGenType(String genType) + { + this.genType = genType; + } + + public String getGenPath() + { + return genPath; + } + + public void setGenPath(String genPath) + { + this.genPath = genPath; + } + + public GenTableColumn getPkColumn() + { + return pkColumn; + } + + public void setPkColumn(GenTableColumn pkColumn) + { + this.pkColumn = pkColumn; + } + + public GenTable getSubTable() + { + return subTable; + } + + public void setSubTable(GenTable subTable) + { + this.subTable = subTable; + } + + public List getColumns() + { + return columns; + } + + public void setColumns(List columns) + { + this.columns = columns; + } + + public String getOptions() + { + return options; + } + + public void setOptions(String options) + { + this.options = options; + } + + public String getTreeCode() + { + return treeCode; + } + + public void setTreeCode(String treeCode) + { + this.treeCode = treeCode; + } + + public String getTreeParentCode() + { + return treeParentCode; + } + + public void setTreeParentCode(String treeParentCode) + { + this.treeParentCode = treeParentCode; + } + + public String getTreeName() + { + return treeName; + } + + public void setTreeName(String treeName) + { + this.treeName = treeName; + } + + public Long getParentMenuId() + { + return parentMenuId; + } + + public void setParentMenuId(Long parentMenuId) + { + this.parentMenuId = parentMenuId; + } + + public String getParentMenuName() + { + return parentMenuName; + } + + public void setParentMenuName(String parentMenuName) + { + this.parentMenuName = parentMenuName; + } + + public boolean isSub() + { + return isSub(this.tplCategory); + } + + public static boolean isSub(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + public boolean isTree() + { + return isTree(this.tplCategory); + } + + public static boolean isTree(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() + { + return isCrud(this.tplCategory); + } + + public static boolean isCrud(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public boolean isSuperColumn(String javaField) + { + return isSuperColumn(this.tplCategory, javaField); + } + + public static boolean isSuperColumn(String tplCategory, String javaField) + { + if (isTree(tplCategory)) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/vetti-generator/src/main/java/com/vetti/generator/domain/GenTableColumn.java b/vetti-generator/src/main/java/com/vetti/generator/domain/GenTableColumn.java new file mode 100644 index 0000000..06a3039 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/domain/GenTableColumn.java @@ -0,0 +1,373 @@ +package com.vetti.generator.domain; + +import javax.validation.constraints.NotBlank; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.utils.StringUtils; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author ruoyi + */ +public class GenTableColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long columnId; + + /** 归属表编号 */ + private Long tableId; + + /** 列名称 */ + private String columnName; + + /** 列描述 */ + private String columnComment; + + /** 列类型 */ + private String columnType; + + /** JAVA类型 */ + private String javaType; + + /** JAVA字段名 */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + private String isPk; + + /** 是否自增(1是) */ + private String isIncrement; + + /** 是否必填(1是) */ + private String isRequired; + + /** 是否为插入字段(1是) */ + private String isInsert; + + /** 是否编辑字段(1是) */ + private String isEdit; + + /** 是否列表字段(1是) */ + private String isList; + + /** 是否查询字段(1是) */ + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + private String queryType; + + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ + private String htmlType; + + /** 字典类型 */ + private String dictType; + + /** 排序 */ + private Integer sort; + + public void setColumnId(Long columnId) + { + this.columnId = columnId; + } + + public Long getColumnId() + { + return columnId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public Long getTableId() + { + return tableId; + } + + public void setColumnName(String columnName) + { + this.columnName = columnName; + } + + public String getColumnName() + { + return columnName; + } + + public void setColumnComment(String columnComment) + { + this.columnComment = columnComment; + } + + public String getColumnComment() + { + return columnComment; + } + + public void setColumnType(String columnType) + { + this.columnType = columnType; + } + + public String getColumnType() + { + return columnType; + } + + public void setJavaType(String javaType) + { + this.javaType = javaType; + } + + public String getJavaType() + { + return javaType; + } + + public void setJavaField(String javaField) + { + this.javaField = javaField; + } + + public String getJavaField() + { + return javaField; + } + + public String getCapJavaField() + { + return StringUtils.capitalize(javaField); + } + + public void setIsPk(String isPk) + { + this.isPk = isPk; + } + + public String getIsPk() + { + return isPk; + } + + public boolean isPk() + { + return isPk(this.isPk); + } + + public boolean isPk(String isPk) + { + return isPk != null && StringUtils.equals("1", isPk); + } + + public String getIsIncrement() + { + return isIncrement; + } + + public void setIsIncrement(String isIncrement) + { + this.isIncrement = isIncrement; + } + + public boolean isIncrement() + { + return isIncrement(this.isIncrement); + } + + public boolean isIncrement(String isIncrement) + { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public void setIsRequired(String isRequired) + { + this.isRequired = isRequired; + } + + public String getIsRequired() + { + return isRequired; + } + + public boolean isRequired() + { + return isRequired(this.isRequired); + } + + public boolean isRequired(String isRequired) + { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public void setIsInsert(String isInsert) + { + this.isInsert = isInsert; + } + + public String getIsInsert() + { + return isInsert; + } + + public boolean isInsert() + { + return isInsert(this.isInsert); + } + + public boolean isInsert(String isInsert) + { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public void setIsEdit(String isEdit) + { + this.isEdit = isEdit; + } + + public String getIsEdit() + { + return isEdit; + } + + public boolean isEdit() + { + return isInsert(this.isEdit); + } + + public boolean isEdit(String isEdit) + { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public void setIsList(String isList) + { + this.isList = isList; + } + + public String getIsList() + { + return isList; + } + + public boolean isList() + { + return isList(this.isList); + } + + public boolean isList(String isList) + { + return isList != null && StringUtils.equals("1", isList); + } + + public void setIsQuery(String isQuery) + { + this.isQuery = isQuery; + } + + public String getIsQuery() + { + return isQuery; + } + + public boolean isQuery() + { + return isQuery(this.isQuery); + } + + public boolean isQuery(String isQuery) + { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public void setQueryType(String queryType) + { + this.queryType = queryType; + } + + public String getQueryType() + { + return queryType; + } + + public String getHtmlType() + { + return htmlType; + } + + public void setHtmlType(String htmlType) + { + this.htmlType = htmlType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getDictType() + { + return dictType; + } + + public void setSort(Integer sort) + { + this.sort = sort; + } + + public Integer getSort() + { + return sort; + } + + public boolean isSuperColumn() + { + return isSuperColumn(this.javaField); + } + + public static boolean isSuperColumn(String javaField) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isUsableColumn() + { + return isUsableColumn(javaField); + } + + public static boolean isUsableColumn(String javaField) + { + // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + public String readConverterExp() + { + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) + { + for (String value : remarks.split(" ")) + { + if (StringUtils.isNotEmpty(value)) + { + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } + else + { + return this.columnComment; + } + } +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableColumnMapper.java b/vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..9961817 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.vetti.generator.mapper; + +import java.util.List; +import com.vetti.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author ruoyi + */ +public interface GenTableColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableMapper.java b/vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..2586949 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/mapper/GenTableMapper.java @@ -0,0 +1,91 @@ +package com.vetti.generator.mapper; + +import java.util.List; +import com.vetti.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author ruoyi + */ +public interface GenTableMapper +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); + + /** + * 创建表 + * + * @param sql 表结构 + * @return 结果 + */ + public int createTable(String sql); +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/service/GenTableColumnServiceImpl.java b/vetti-generator/src/main/java/com/vetti/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 0000000..81fba1f --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.vetti.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.common.core.text.Convert; +import com.vetti.generator.domain.GenTableColumn; +import com.vetti.generator.mapper.GenTableColumnMapper; + +/** + * 业务字段 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int insertGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int updateGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 删除业务字段对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/service/GenTableServiceImpl.java b/vetti-generator/src/main/java/com/vetti/generator/service/GenTableServiceImpl.java new file mode 100644 index 0000000..ccea1ba --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/service/GenTableServiceImpl.java @@ -0,0 +1,531 @@ +package com.vetti.generator.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.GenConstants; +import com.vetti.common.core.text.CharsetKit; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.StringUtils; +import com.vetti.generator.domain.GenTable; +import com.vetti.generator.domain.GenTableColumn; +import com.vetti.generator.mapper.GenTableColumnMapper; +import com.vetti.generator.mapper.GenTableMapper; +import com.vetti.generator.util.GenUtils; +import com.vetti.generator.util.VelocityInitializer; +import com.vetti.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableServiceImpl implements IGenTableService +{ + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + @Autowired + private GenTableMapper genTableMapper; + + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + @Override + public GenTable selectGenTableById(Long id) + { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + @Override + public List selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) + { + return genTableMapper.selectDbTableListByNames(tableNames); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List selectGenTableAll() + { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) + { + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) + { + for (GenTableColumn genTableColumn : genTable.getColumns()) + { + genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param tableIds 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) + { + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + @Override + public boolean createTable(String sql) + { + return genTableMapper.createTable(sql) == 0; + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List tableList, String operName) + { + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 保存列信息 + List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + for (GenTableColumn column : genTableColumns) + { + GenUtils.initColumnField(column, table); + genTableColumnMapper.insertGenTableColumn(column); + } + } + } + } + catch (Exception e) + { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + @Override + public Map previewCode(Long tableId) + { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + @Override + public byte[] downloadCode(String tableName) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + @Override + public void generatorCode(String tableName) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) + { + if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } + catch (IOException e) + { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + } + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + @Override + @Transactional + public void synchDb(String tableName) + { + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); + + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) + { + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) + { + // 如果是列表,继续保留查询方式/字典类型选项 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) + { + // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } + else + { + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + List delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) + { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + @Override + public byte[] downloadCode(String[] tableNames) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) + { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(String tableName, ZipOutputStream zip) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } + catch (IOException e) + { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + @Override + public void validateEdit(GenTable genTable) + { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) + { + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSON.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) + { + throw new ServiceException("树编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) + { + throw new ServiceException("树父编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) + { + throw new ServiceException("树名称字段不能为空"); + } + } + else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) + { + if (StringUtils.isEmpty(genTable.getSubTableName())) + { + throw new ServiceException("关联子表的表名不能为空"); + } + else if (StringUtils.isEmpty(genTable.getSubTableFkName())) + { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) + { + for (GenTableColumn column : table.getColumns()) + { + if (column.isPk()) + { + table.setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getPkColumn())) + { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) + { + for (GenTableColumn column : table.getSubTable().getColumns()) + { + if (column.isPk()) + { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) + { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) + { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) + { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) + { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) + { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + /** + * 获取代码生成地址 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成地址 + */ + public static String getGenPath(GenTable table, String template) + { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) + { + return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table); + } + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} \ No newline at end of file diff --git a/vetti-generator/src/main/java/com/vetti/generator/service/IGenTableColumnService.java b/vetti-generator/src/main/java/com/vetti/generator/service/IGenTableColumnService.java new file mode 100644 index 0000000..e5fbec1 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.vetti.generator.service; + +import java.util.List; +import com.vetti.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author ruoyi + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/service/IGenTableService.java b/vetti-generator/src/main/java/com/vetti/generator/service/IGenTableService.java new file mode 100644 index 0000000..966aad1 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/service/IGenTableService.java @@ -0,0 +1,130 @@ +package com.vetti.generator.service; + +import java.util.List; +import java.util.Map; +import com.vetti.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author ruoyi + */ +public interface IGenTableService +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + public boolean createTable(String sql); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + * @param operName 操作人员 + */ + public void importGenTable(List tableList, String operName); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/util/GenUtils.java b/vetti-generator/src/main/java/com/vetti/generator/util/GenUtils.java new file mode 100644 index 0000000..077b8b5 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/util/GenUtils.java @@ -0,0 +1,257 @@ +package com.vetti.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.vetti.common.constant.GenConstants; +import com.vetti.common.utils.StringUtils; +import com.vetti.generator.config.GenConfig; +import com.vetti.generator.domain.GenTable; +import com.vetti.generator.domain.GenTableColumn; + +/** + * 代码生成器 工具类 + * + * @author ruoyi + */ +public class GenUtils +{ + /** + * 初始化表信息 + */ + public static void initTable(GenTable genTable, String operName) + { + genTable.setClassName(convertClassName(genTable.getTableName())); + genTable.setPackageName(GenConfig.getPackageName()); + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + genTable.setFunctionName(replaceText(genTable.getTableComment())); + genTable.setFunctionAuthor(GenConfig.getAuthor()); + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenTableColumn column, GenTable table) + { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + // 设置java字段名 + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); + + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) + { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } + // 如果是整形 + else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) + { + column.setJavaType(GenConstants.TYPE_INTEGER); + } + // 长整形 + else + { + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) + { + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacementm 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换 + * + * @param text 需要被替换的名字 + * @return 替换后的名字 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|若依)", ""); + } + + /** + * 获取数据库类型字段 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/util/VelocityInitializer.java b/vetti-generator/src/main/java/com/vetti/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..1d6c383 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.vetti.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.vetti.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author ruoyi + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/vetti-generator/src/main/java/com/vetti/generator/util/VelocityUtils.java b/vetti-generator/src/main/java/com/vetti/generator/util/VelocityUtils.java new file mode 100644 index 0000000..44da6e3 --- /dev/null +++ b/vetti-generator/src/main/java/com/vetti/generator/util/VelocityUtils.java @@ -0,0 +1,408 @@ +package com.vetti.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.vetti.common.constant.GenConstants; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.generator.domain.GenTable; +import com.vetti.generator.domain.GenTableColumn; + +/** + * 模板处理工具类 + * + * @author ruoyi + */ +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTable genTable) + { + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); + velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + velocityContext.put("datetime", DateUtils.getDate()); + velocityContext.put("pkColumn", genTable.getPkColumn()); + velocityContext.put("importList", getImportList(genTable)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTable.getColumns()); + velocityContext.put("table", genTable); + velocityContext.put("dicts", getDicts(genTable)); + setMenuVelocityContext(velocityContext, genTable); + if (GenConstants.TPL_TREE.equals(tplCategory)) + { + setTreeVelocityContext(velocityContext, genTable); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) + { + setSubVelocityContext(velocityContext, genTable); + } + return velocityContext; + } + + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) + { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = genTable.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTable.getSubTable())); + } + + /** + * 获取模板信息 + * @param tplCategory 生成的模板 + * @param tplWebType 前端类型 + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory, String tplWebType) + { + String useWebType = "vm/vue"; + if ("element-plus".equals(tplWebType)) + { + useWebType = "vm/vue/v3"; + } + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add(useWebType + "/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template, GenTable genTable) + { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTable.getPackageName(); + // 模块名 + String moduleName = genTable.getModuleName(); + // 大写类名 + String className = genTable.getClassName(); + // 业务名称 + String businessName = genTable.getBusinessName(); + + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String vuePath = "vue"; + + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } + if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } + else if (template.contains("mapper.java.vm")) + { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } + else if (template.contains("service.java.vm")) + { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } + else if (template.contains("serviceImpl.java.vm")) + { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } + else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } + else if (template.contains("mapper.xml.vm")) + { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } + else if (template.contains("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTable 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + if (StringUtils.isNotNull(subGenTable)) + { + importList.add("java.util.List"); + } + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) + { + importList.add("java.util.Date"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) + { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 根据列类型获取字典组 + * + * @param genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) + { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) + { + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) + { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) + { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) + { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreecode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 获取需要在哪一列上面显示展开按钮 + * + * @param genTable 业务表对象 + * @return 展开按钮列序号 + */ + public static int getExpandColumn(GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenTableColumn column : genTable.getColumns()) + { + if (column.isList()) + { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) + { + break; + } + } + } + return num; + } +} diff --git a/vetti-generator/src/main/resources/generator.yml b/vetti-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..35dd0bc --- /dev/null +++ b/vetti-generator/src/main/resources/generator.yml @@ -0,0 +1,12 @@ +# 代码生成 +gen: + # 作者 + author: admin + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.vetti.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ + # 是否允许生成文件覆盖到本地(自定义路径),默认不允许 + allowOverwrite: false \ No newline at end of file diff --git a/vetti-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/vetti-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..5bb8b9e --- /dev/null +++ b/vetti-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/vetti-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/vetti-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..de0a42d --- /dev/null +++ b/vetti-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + ${sql} + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/vetti-generator/src/main/resources/vm/java/controller.java.vm b/vetti-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..b18a7c8 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,123 @@ +package ${packageName}.controller; + +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.enums.BusinessType; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.vetti.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.vetti.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@Api(tags ="${functionName}") +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @ApiOperation("查询${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return AjaxResult.success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @ApiOperation("导出${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @GetMapping("/export") + public AjaxResult export(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + return util.exportExcel(list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @ApiOperation("获取${functionName}详细信息") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return AjaxResult.success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @ApiOperation("新增${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @ApiOperation("修改${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @ApiOperation("删除${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/vetti-generator/src/main/resources/vm/java/domain.java.vm b/vetti-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..7c46656 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import lombok.Data; +import lombok.experimental.Accessors; +import io.swagger.annotations.ApiModelProperty; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +#if($table.crud || $table.sub) +#elseif($table.tree) +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +@Data +@Accessors(chain = true) +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ + @ApiModelProperty("$column.columnComment") +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +} diff --git a/vetti-generator/src/main/resources/vm/java/mapper.java.vm b/vetti-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..0f31a8b --- /dev/null +++ b/vetti-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,99 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end + /** + * 批量新增${functionName} + * + * @param ${className}List ${functionName}列表 + * @return 结果 + */ + public int batchInsert${ClassName}(List<${ClassName}> ${className}List); + +} diff --git a/vetti-generator/src/main/resources/vm/java/service.java.vm b/vetti-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..b2bc904 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,70 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量新增${functionName} + * + * @param ${className}List ${functionName}列表 + * @return 结果 + */ + public int batchInsert${ClassName}(List<${ClassName}> ${className}List); + +} diff --git a/vetti-generator/src/main/resources/vm/java/serviceImpl.java.vm b/vetti-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..89a5dbc --- /dev/null +++ b/vetti-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,179 @@ +package ${packageName}.service.impl; + +import java.util.List; + +import com.vetti.common.core.service.BaseServiceImpl; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.vetti.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.vetti.common.utils.StringUtils; +import ${packageName}.domain.${subClassName}; +#end +import org.springframework.transaction.annotation.Transactional; + +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@SuppressWarnings("all") +@Service +public class ${ClassName}ServiceImpl extends BaseServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Transactional(readOnly = true) + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Transactional(readOnly = true) + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + @Transactional(rollbackFor=Exception.class) + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end + /** + * 批量新增${functionName} + * + * @param ${className}List ${functionName}列表 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int batchInsert${ClassName}(List<${ClassName}> ${className}List){ + return ${className}Mapper.batchInsert${ClassName}(${className}List); + } +} diff --git a/vetti-generator/src/main/resources/vm/java/sub-domain.java.vm b/vetti-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..d8d9bd9 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,73 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import com.sw.common.annotation.Excel; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/vetti-generator/src/main/resources/vm/js/api.js.vm b/vetti-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/vetti-generator/src/main/resources/vm/sql/sql.vm b/vetti-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/vetti-generator/src/main/resources/vm/vue/index-tree.vue.vm b/vetti-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..4e35fc9 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,505 @@ + + + diff --git a/vetti-generator/src/main/resources/vm/vue/index.vue.vm b/vetti-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 0000000..04ebe16 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,602 @@ + + + diff --git a/vetti-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/vetti-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..765a5e3 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/vetti-generator/src/main/resources/vm/vue/v3/index.vue.vm b/vetti-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..936b465 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ + + + diff --git a/vetti-generator/src/main/resources/vm/xml/mapper.xml.vm b/vetti-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..94479a3 --- /dev/null +++ b/vetti-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,142 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + + + insert into ${tableName}(#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end) values + + (#foreach($column in $columns) #{item.$column.javaField}#if($velocityCount != $columns.size()),#end#end) + + + \ No newline at end of file diff --git a/vetti-generator/target/classes/generator.yml b/vetti-generator/target/classes/generator.yml new file mode 100644 index 0000000..35dd0bc --- /dev/null +++ b/vetti-generator/target/classes/generator.yml @@ -0,0 +1,12 @@ +# 代码生成 +gen: + # 作者 + author: admin + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.vetti.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ + # 是否允许生成文件覆盖到本地(自定义路径),默认不允许 + allowOverwrite: false \ No newline at end of file diff --git a/vetti-generator/target/classes/mapper/generator/GenTableColumnMapper.xml b/vetti-generator/target/classes/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..5bb8b9e --- /dev/null +++ b/vetti-generator/target/classes/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/vetti-generator/target/classes/mapper/generator/GenTableMapper.xml b/vetti-generator/target/classes/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..de0a42d --- /dev/null +++ b/vetti-generator/target/classes/mapper/generator/GenTableMapper.xml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + ${sql} + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/vetti-generator/target/classes/vm/java/controller.java.vm b/vetti-generator/target/classes/vm/java/controller.java.vm new file mode 100644 index 0000000..b18a7c8 --- /dev/null +++ b/vetti-generator/target/classes/vm/java/controller.java.vm @@ -0,0 +1,123 @@ +package ${packageName}.controller; + +import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.enums.BusinessType; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.vetti.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.vetti.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@Api(tags ="${functionName}") +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @ApiOperation("查询${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return AjaxResult.success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @ApiOperation("导出${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @GetMapping("/export") + public AjaxResult export(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + return util.exportExcel(list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @ApiOperation("获取${functionName}详细信息") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return AjaxResult.success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @ApiOperation("新增${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @ApiOperation("修改${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @ApiOperation("删除${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/vetti-generator/target/classes/vm/java/domain.java.vm b/vetti-generator/target/classes/vm/java/domain.java.vm new file mode 100644 index 0000000..7c46656 --- /dev/null +++ b/vetti-generator/target/classes/vm/java/domain.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import lombok.Data; +import lombok.experimental.Accessors; +import io.swagger.annotations.ApiModelProperty; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; +#if($table.crud || $table.sub) +#elseif($table.tree) +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +@Data +@Accessors(chain = true) +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ + @ApiModelProperty("$column.columnComment") +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +} diff --git a/vetti-generator/target/classes/vm/java/mapper.java.vm b/vetti-generator/target/classes/vm/java/mapper.java.vm new file mode 100644 index 0000000..0f31a8b --- /dev/null +++ b/vetti-generator/target/classes/vm/java/mapper.java.vm @@ -0,0 +1,99 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end + /** + * 批量新增${functionName} + * + * @param ${className}List ${functionName}列表 + * @return 结果 + */ + public int batchInsert${ClassName}(List<${ClassName}> ${className}List); + +} diff --git a/vetti-generator/target/classes/vm/java/service.java.vm b/vetti-generator/target/classes/vm/java/service.java.vm new file mode 100644 index 0000000..b2bc904 --- /dev/null +++ b/vetti-generator/target/classes/vm/java/service.java.vm @@ -0,0 +1,70 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量新增${functionName} + * + * @param ${className}List ${functionName}列表 + * @return 结果 + */ + public int batchInsert${ClassName}(List<${ClassName}> ${className}List); + +} diff --git a/vetti-generator/target/classes/vm/java/serviceImpl.java.vm b/vetti-generator/target/classes/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..89a5dbc --- /dev/null +++ b/vetti-generator/target/classes/vm/java/serviceImpl.java.vm @@ -0,0 +1,179 @@ +package ${packageName}.service.impl; + +import java.util.List; + +import com.vetti.common.core.service.BaseServiceImpl; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.vetti.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.vetti.common.utils.StringUtils; +import ${packageName}.domain.${subClassName}; +#end +import org.springframework.transaction.annotation.Transactional; + +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@SuppressWarnings("all") +@Service +public class ${ClassName}ServiceImpl extends BaseServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Transactional(readOnly = true) + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Transactional(readOnly = true) + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + @Transactional(rollbackFor=Exception.class) + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end + /** + * 批量新增${functionName} + * + * @param ${className}List ${functionName}列表 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int batchInsert${ClassName}(List<${ClassName}> ${className}List){ + return ${className}Mapper.batchInsert${ClassName}(${className}List); + } +} diff --git a/vetti-generator/target/classes/vm/java/sub-domain.java.vm b/vetti-generator/target/classes/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..d8d9bd9 --- /dev/null +++ b/vetti-generator/target/classes/vm/java/sub-domain.java.vm @@ -0,0 +1,73 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import com.sw.common.annotation.Excel; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/vetti-generator/target/classes/vm/js/api.js.vm b/vetti-generator/target/classes/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/vetti-generator/target/classes/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/vetti-generator/target/classes/vm/sql/sql.vm b/vetti-generator/target/classes/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/vetti-generator/target/classes/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/vetti-generator/target/classes/vm/vue/index-tree.vue.vm b/vetti-generator/target/classes/vm/vue/index-tree.vue.vm new file mode 100644 index 0000000..4e35fc9 --- /dev/null +++ b/vetti-generator/target/classes/vm/vue/index-tree.vue.vm @@ -0,0 +1,505 @@ + + + diff --git a/vetti-generator/target/classes/vm/vue/index.vue.vm b/vetti-generator/target/classes/vm/vue/index.vue.vm new file mode 100644 index 0000000..04ebe16 --- /dev/null +++ b/vetti-generator/target/classes/vm/vue/index.vue.vm @@ -0,0 +1,602 @@ + + + diff --git a/vetti-generator/target/classes/vm/vue/v3/index-tree.vue.vm b/vetti-generator/target/classes/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..765a5e3 --- /dev/null +++ b/vetti-generator/target/classes/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/vetti-generator/target/classes/vm/vue/v3/index.vue.vm b/vetti-generator/target/classes/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..936b465 --- /dev/null +++ b/vetti-generator/target/classes/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ + + + diff --git a/vetti-generator/target/classes/vm/xml/mapper.xml.vm b/vetti-generator/target/classes/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..94479a3 --- /dev/null +++ b/vetti-generator/target/classes/vm/xml/mapper.xml.vm @@ -0,0 +1,142 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + + + insert into ${tableName}(#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end) values + + (#foreach($column in $columns) #{item.$column.javaField}#if($velocityCount != $columns.size()),#end#end) + + + \ No newline at end of file diff --git a/vetti-quartz/pom.xml b/vetti-quartz/pom.xml new file mode 100644 index 0000000..63b28f0 --- /dev/null +++ b/vetti-quartz/pom.xml @@ -0,0 +1,40 @@ + + + + vetti-service + com.vetti + 3.9.0 + + 4.0.0 + + vetti-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.vetti + vetti-common + + + + + \ No newline at end of file diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/config/ScheduleConfig.java b/vetti-quartz/src/main/java/com/vetti/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..d4e065a --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.ruoyi.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效) +// * +// * @author ruoyi +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("RuoyiScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobController.java b/vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobController.java new file mode 100644 index 0000000..51883c4 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobController.java @@ -0,0 +1,185 @@ +package com.vetti.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +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.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 com.vetti.common.annotation.Log; +import com.vetti.common.constant.Constants; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.exception.job.TaskException; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.quartz.domain.SysJob; +import com.vetti.quartz.service.ISysJobService; +import com.vetti.quartz.util.CronUtils; +import com.vetti.quartz.util.ScheduleUtils; + +/** + * 调度任务信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { + startPage(); + List list = jobService.selectJobList(sysJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJob sysJob) + { + List list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setCreateBy(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException + { + jobService.deleteJobByIds(jobIds); + return success(); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobLogController.java b/vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..0179990 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/controller/SysJobLogController.java @@ -0,0 +1,92 @@ +package com.vetti.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +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.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.vetti.common.annotation.Log; +import com.vetti.common.core.controller.BaseController; +import com.vetti.common.core.domain.AjaxResult; +import com.vetti.common.core.page.TableDataInfo; +import com.vetti.common.enums.BusinessType; +import com.vetti.common.utils.poi.ExcelUtil; +import com.vetti.quartz.domain.SysJobLog; +import com.vetti.quartz.service.ISysJobLogService; + +/** + * 调度日志操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { + startPage(); + List list = jobLogService.selectJobLogList(sysJobLog); + return getDataTable(list); + } + + /** + * 导出定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJobLog sysJobLog) + { + List list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); + } + + /** + * 根据调度编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); + } + + + /** + * 删除定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); + } + + /** + * 清空定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJob.java b/vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJob.java new file mode 100644 index 0000000..7d4a45b --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJob.java @@ -0,0 +1,171 @@ +package com.vetti.quartz.domain; + +import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.constant.ScheduleConstants; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.utils.StringUtils; +import com.vetti.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author ruoyi + */ +public class SysJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式 */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJobLog.java b/vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..5259fd1 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/domain/SysJobLog.java @@ -0,0 +1,155 @@ +package com.vetti.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author ruoyi + */ +public class SysJobLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "日志序号") + private Long jobLogId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息 */ + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") + private String status; + + /** 异常信息 */ + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间 */ + private Date startTime; + + /** 停止时间 */ + private Date stopTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getStopTime() + { + return stopTime; + } + + public void setStopTime(Date stopTime) + { + this.stopTime = stopTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("stopTime", getStopTime()) + .toString(); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobLogMapper.java b/vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..ca8c067 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.vetti.quartz.mapper; + +import java.util.List; +import com.vetti.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author ruoyi + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobMapper.java b/vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..fd6920e --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.vetti.quartz.mapper; + +import java.util.List; +import com.vetti.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author ruoyi + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobLogService.java b/vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..ed1f7b9 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.vetti.quartz.service; + +import java.util.List; +import com.vetti.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobService.java b/vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobService.java new file mode 100644 index 0000000..a0b1f63 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.vetti.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.vetti.common.exception.job.TaskException; +import com.vetti.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobLogServiceImpl.java b/vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..ad733b0 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.vetti.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.quartz.domain.SysJobLog; +import com.vetti.quartz.mapper.SysJobLogMapper; +import com.vetti.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobServiceImpl.java b/vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..37b7219 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,261 @@ +package com.vetti.quartz.service.impl; + +import java.util.List; +import javax.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.vetti.common.constant.ScheduleConstants; +import com.vetti.common.exception.job.TaskException; +import com.vetti.quartz.domain.SysJob; +import com.vetti.quartz.mapper.SysJobMapper; +import com.vetti.quartz.service.ISysJobService; +import com.vetti.quartz.util.CronUtils; +import com.vetti.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobServiceImpl implements ISysJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private SysJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(SysJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) + { + SysJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + result = true; + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * + * @param job 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param job 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/task/RyTask.java b/vetti-quartz/src/main/java/com/vetti/quartz/task/RyTask.java new file mode 100644 index 0000000..ebdc4df --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.vetti.quartz.task; + +import org.springframework.stereotype.Component; +import com.vetti.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author ruoyi + */ +@Component("ryTask") +public class RyTask +{ + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/util/AbstractQuartzJob.java b/vetti-quartz/src/main/java/com/vetti/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..6cc62f5 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,106 @@ +package com.vetti.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.ScheduleConstants; +import com.vetti.common.utils.ExceptionUtil; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.bean.BeanUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.quartz.domain.SysJob; +import com.vetti.quartz.domain.SysJobLog; +import com.vetti.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author ruoyi + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) + { + SysJob sysJob = new SysJob(); + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, sysJob); + if (sysJob != null) + { + doExecute(context, sysJob); + } + after(context, sysJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, sysJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + sysJobLog.setStartTime(startTime); + sysJobLog.setStopTime(new Date()); + long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/util/CronUtils.java b/vetti-quartz/src/main/java/com/vetti/quartz/util/CronUtils.java new file mode 100644 index 0000000..218dcad --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.vetti.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * + * @author ruoyi + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/util/JobInvokeUtil.java b/vetti-quartz/src/main/java/com/vetti/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..8d3e86e --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.vetti.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author ruoyi + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @param sysJob 系统任务 + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + String invokeTarget = sysJob.getInvokeTarget(); + String beanName = getBeanName(invokeTarget); + String methodName = getMethodName(invokeTarget); + List methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param invokeTarget 名称 + * @return true是 false否 + */ + public static boolean isValidClassName(String invokeTarget) + { + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 获取bean名称 + * + * @param invokeTarget 目标字符串 + * @return bean名称 + */ + public static String getBeanName(String invokeTarget) + { + String beanName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 获取bean方法 + * + * @param invokeTarget 目标字符串 + * @return method方法 + */ + public static String getMethodName(String invokeTarget) + { + String methodName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 获取method方法参数相关列表 + * + * @param invokeTarget 目标字符串 + * @return method方法相关参数列表 + */ + public static List getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetweenLast(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // boolean布尔类型,等于true或者false + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List methodParams) + { + Class[] classs = new Class[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 获取参数值 + * + * @param methodParams 参数相关列表 + * @return 参数值列表 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzDisallowConcurrentExecution.java b/vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..732d32a --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.vetti.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.vetti.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author ruoyi + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzJobExecution.java b/vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..f8e6524 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.vetti.quartz.util; + +import org.quartz.JobExecutionContext; +import com.vetti.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author ruoyi + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/vetti-quartz/src/main/java/com/vetti/quartz/util/ScheduleUtils.java b/vetti-quartz/src/main/java/com/vetti/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..b3cac41 --- /dev/null +++ b/vetti-quartz/src/main/java/com/vetti/quartz/util/ScheduleUtils.java @@ -0,0 +1,141 @@ +package com.vetti.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.ScheduleConstants; +import com.vetti.common.exception.job.TaskException; +import com.vetti.common.exception.job.TaskException.Code; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author ruoyi + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException + { + Class jobClass = getQuartzJobClass(job); + // 构建job信息 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 判断任务是否过期 + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) + { + // 执行调度任务 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException + { + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查包名是否为白名单配置 + * + * @param invokeTarget 目标字符串 + * @return 结果 + */ + public static boolean whiteList(String invokeTarget) + { + String packageName = StringUtils.substringBefore(invokeTarget, "("); + int count = StringUtils.countMatches(packageName, "."); + if (count > 1) + { + return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR); + } +} diff --git a/vetti-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/vetti-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..0d48a6e --- /dev/null +++ b/vetti-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/vetti-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..26a5133 --- /dev/null +++ b/vetti-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml b/vetti-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..0d48a6e --- /dev/null +++ b/vetti-quartz/target/classes/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-quartz/target/classes/mapper/quartz/SysJobMapper.xml b/vetti-quartz/target/classes/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..26a5133 --- /dev/null +++ b/vetti-quartz/target/classes/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-system/pom.xml b/vetti-system/pom.xml new file mode 100644 index 0000000..9a6703c --- /dev/null +++ b/vetti-system/pom.xml @@ -0,0 +1,39 @@ + + + + vetti-service + com.vetti + 3.9.0 + + 4.0.0 + + vetti-system + + + system系统模块 + + + + + + + io.swagger + swagger-models + + + + org.projectlombok + lombok + + + + + com.vetti + vetti-common + + + + + \ No newline at end of file diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysCache.java b/vetti-system/src/main/java/com/vetti/system/domain/SysCache.java new file mode 100644 index 0000000..928431d --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysCache.java @@ -0,0 +1,81 @@ +package com.vetti.system.domain; + +import com.vetti.common.utils.StringUtils; + +/** + * 缓存信息 + * + * @author ruoyi + */ +public class SysCache +{ + /** 缓存名称 */ + private String cacheName = ""; + + /** 缓存键名 */ + private String cacheKey = ""; + + /** 缓存内容 */ + private String cacheValue = ""; + + /** 备注 */ + private String remark = ""; + + public SysCache() + { + + } + + public SysCache(String cacheName, String remark) + { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) + { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + + public String getCacheName() + { + return cacheName; + } + + public void setCacheName(String cacheName) + { + this.cacheName = cacheName; + } + + public String getCacheKey() + { + return cacheKey; + } + + public void setCacheKey(String cacheKey) + { + this.cacheKey = cacheKey; + } + + public String getCacheValue() + { + return cacheValue; + } + + public void setCacheValue(String cacheValue) + { + this.cacheValue = cacheValue; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysConfig.java b/vetti-system/src/main/java/com/vetti/system/domain/SysConfig.java new file mode 100644 index 0000000..7080ed5 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysConfig.java @@ -0,0 +1,111 @@ +package com.vetti.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 参数配置表 sys_config + * + * @author ruoyi + */ +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) + private Long configId; + + /** 参数名称 */ + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") + private String configType; + + public Long getConfigId() + { + return configId; + } + + public void setConfigId(Long configId) + { + this.configId = configId; + } + + @NotBlank(message = "参数名称不能为空") + @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") + public String getConfigName() + { + return configName; + } + + public void setConfigName(String configName) + { + this.configName = configName; + } + + @NotBlank(message = "参数键名长度不能为空") + @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") + public String getConfigKey() + { + return configKey; + } + + public void setConfigKey(String configKey) + { + this.configKey = configKey; + } + + @NotBlank(message = "参数键值不能为空") + @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") + public String getConfigValue() + { + return configValue; + } + + public void setConfigValue(String configValue) + { + this.configValue = configValue; + } + + public String getConfigType() + { + return configType; + } + + public void setConfigType(String configType) + { + this.configType = configType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("configId", getConfigId()) + .append("configName", getConfigName()) + .append("configKey", getConfigKey()) + .append("configValue", getConfigValue()) + .append("configType", getConfigType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysInternationalInfo.java b/vetti-system/src/main/java/com/vetti/system/domain/SysInternationalInfo.java new file mode 100644 index 0000000..2b2c552 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysInternationalInfo.java @@ -0,0 +1,51 @@ +package com.vetti.system.domain; + +import lombok.Data; +import lombok.experimental.Accessors; +import io.swagger.annotations.ApiModelProperty; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 数据国际化映射对象 sys_international_info + * + * @author wangxiangshun + * @date 2025-08-26 + */ +@Data +@Accessors(chain = true) +public class SysInternationalInfo extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @ApiModelProperty("ID") + private Long id; + + /** 资源ID */ + @ApiModelProperty("资源ID") + @Excel(name = "资源ID") + private String resourceId; + + /** 资源类型 */ + @ApiModelProperty("资源类型") + @Excel(name = "资源类型") + private String resourceType; + + /** 资源名称(中文) */ + @ApiModelProperty("资源名称(中文)") + @Excel(name = "资源名称(中文)") + private String resourceZhText; + + /** 资源名称(英文) */ + @ApiModelProperty("资源名称(英文)") + @Excel(name = "资源名称(英文)") + private String resourceEnText; + + /** 资源名称(日文) */ + @ApiModelProperty("资源名称(日文)") + @Excel(name = "资源名称(日文)") + private String resourceJaText; + + +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysLogininfor.java b/vetti-system/src/main/java/com/vetti/system/domain/SysLogininfor.java new file mode 100644 index 0000000..4abb1ef --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysLogininfor.java @@ -0,0 +1,144 @@ +package com.vetti.system.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 系统访问记录表 sys_logininfor + * + * @author ruoyi + */ +public class SysLogininfor extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "序号", cellType = ColumnType.NUMERIC) + private Long infoId; + + /** 用户账号 */ + @Excel(name = "用户账号") + private String userName; + + /** 登录状态 0成功 1失败 */ + @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") + private String status; + + /** 登录IP地址 */ + @Excel(name = "登录地址") + private String ipaddr; + + /** 登录地点 */ + @Excel(name = "登录地点") + private String loginLocation; + + /** 浏览器类型 */ + @Excel(name = "浏览器") + private String browser; + + /** 操作系统 */ + @Excel(name = "操作系统") + private String os; + + /** 提示消息 */ + @Excel(name = "提示消息") + private String msg; + + /** 访问时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + public Long getInfoId() + { + return infoId; + } + + public void setInfoId(Long infoId) + { + this.infoId = infoId; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public Date getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Date loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysNotice.java b/vetti-system/src/main/java/com/vetti/system/domain/SysNotice.java new file mode 100644 index 0000000..019c67a --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysNotice.java @@ -0,0 +1,102 @@ +package com.vetti.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.core.domain.BaseEntity; +import com.vetti.common.xss.Xss; + +/** + * 通知公告表 sys_notice + * + * @author ruoyi + */ +public class SysNotice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 公告ID */ + private Long noticeId; + + /** 公告标题 */ + private String noticeTitle; + + /** 公告类型(1通知 2公告) */ + private String noticeType; + + /** 公告内容 */ + private String noticeContent; + + /** 公告状态(0正常 1关闭) */ + private String status; + + public Long getNoticeId() + { + return noticeId; + } + + public void setNoticeId(Long noticeId) + { + this.noticeId = noticeId; + } + + public void setNoticeTitle(String noticeTitle) + { + this.noticeTitle = noticeTitle; + } + + @Xss(message = "公告标题不能包含脚本字符") + @NotBlank(message = "公告标题不能为空") + @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") + public String getNoticeTitle() + { + return noticeTitle; + } + + public void setNoticeType(String noticeType) + { + this.noticeType = noticeType; + } + + public String getNoticeType() + { + return noticeType; + } + + public void setNoticeContent(String noticeContent) + { + this.noticeContent = noticeContent; + } + + public String getNoticeContent() + { + return noticeContent; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("noticeId", getNoticeId()) + .append("noticeTitle", getNoticeTitle()) + .append("noticeType", getNoticeType()) + .append("noticeContent", getNoticeContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysOperLog.java b/vetti-system/src/main/java/com/vetti/system/domain/SysOperLog.java new file mode 100644 index 0000000..d6a9bf3 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysOperLog.java @@ -0,0 +1,269 @@ +package com.vetti.system.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 操作日志记录表 oper_log + * + * @author ruoyi + */ +public class SysOperLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 日志主键 */ + @Excel(name = "操作序号", cellType = ColumnType.NUMERIC) + private Long operId; + + /** 操作模块 */ + @Excel(name = "操作模块") + private String title; + + /** 业务类型(0其它 1新增 2修改 3删除) */ + @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据") + private Integer businessType; + + /** 业务类型数组 */ + private Integer[] businessTypes; + + /** 请求方法 */ + @Excel(name = "请求方法") + private String method; + + /** 请求方式 */ + @Excel(name = "请求方式") + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户") + private Integer operatorType; + + /** 操作人员 */ + @Excel(name = "操作人员") + private String operName; + + /** 部门名称 */ + @Excel(name = "部门名称") + private String deptName; + + /** 请求url */ + @Excel(name = "请求地址") + private String operUrl; + + /** 操作地址 */ + @Excel(name = "操作地址") + private String operIp; + + /** 操作地点 */ + @Excel(name = "操作地点") + private String operLocation; + + /** 请求参数 */ + @Excel(name = "请求参数") + private String operParam; + + /** 返回参数 */ + @Excel(name = "返回参数") + private String jsonResult; + + /** 操作状态(0正常 1异常) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=异常") + private Integer status; + + /** 错误消息 */ + @Excel(name = "错误消息") + private String errorMsg; + + /** 操作时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + + /** 消耗时间 */ + @Excel(name = "消耗时间", suffix = "毫秒") + private Long costTime; + + public Long getOperId() + { + return operId; + } + + public void setOperId(Long operId) + { + this.operId = operId; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public Integer getBusinessType() + { + return businessType; + } + + public void setBusinessType(Integer businessType) + { + this.businessType = businessType; + } + + public Integer[] getBusinessTypes() + { + return businessTypes; + } + + public void setBusinessTypes(Integer[] businessTypes) + { + this.businessTypes = businessTypes; + } + + public String getMethod() + { + return method; + } + + public void setMethod(String method) + { + this.method = method; + } + + public String getRequestMethod() + { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) + { + this.requestMethod = requestMethod; + } + + public Integer getOperatorType() + { + return operatorType; + } + + public void setOperatorType(Integer operatorType) + { + this.operatorType = operatorType; + } + + public String getOperName() + { + return operName; + } + + public void setOperName(String operName) + { + this.operName = operName; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getOperUrl() + { + return operUrl; + } + + public void setOperUrl(String operUrl) + { + this.operUrl = operUrl; + } + + public String getOperIp() + { + return operIp; + } + + public void setOperIp(String operIp) + { + this.operIp = operIp; + } + + public String getOperLocation() + { + return operLocation; + } + + public void setOperLocation(String operLocation) + { + this.operLocation = operLocation; + } + + public String getOperParam() + { + return operParam; + } + + public void setOperParam(String operParam) + { + this.operParam = operParam; + } + + public String getJsonResult() + { + return jsonResult; + } + + public void setJsonResult(String jsonResult) + { + this.jsonResult = jsonResult; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public String getErrorMsg() + { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) + { + this.errorMsg = errorMsg; + } + + public Date getOperTime() + { + return operTime; + } + + public void setOperTime(Date operTime) + { + this.operTime = operTime; + } + + public Long getCostTime() + { + return costTime; + } + + public void setCostTime(Long costTime) + { + this.costTime = costTime; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysPost.java b/vetti-system/src/main/java/com/vetti/system/domain/SysPost.java new file mode 100644 index 0000000..3ba1c7b --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysPost.java @@ -0,0 +1,124 @@ +package com.vetti.system.domain; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.vetti.common.annotation.Excel; +import com.vetti.common.annotation.Excel.ColumnType; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 岗位表 sys_post + * + * @author ruoyi + */ +public class SysPost extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 岗位序号 */ + @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) + private Long postId; + + /** 岗位编码 */ + @Excel(name = "岗位编码") + private String postCode; + + /** 岗位名称 */ + @Excel(name = "岗位名称") + private String postName; + + /** 岗位排序 */ + @Excel(name = "岗位排序") + private Integer postSort; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 用户是否存在此岗位标识 默认不存在 */ + private boolean flag = false; + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @NotBlank(message = "岗位编码不能为空") + @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符") + public String getPostCode() + { + return postCode; + } + + public void setPostCode(String postCode) + { + this.postCode = postCode; + } + + @NotBlank(message = "岗位名称不能为空") + @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符") + public String getPostName() + { + return postName; + } + + public void setPostName(String postName) + { + this.postName = postName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getPostSort() + { + return postSort; + } + + public void setPostSort(Integer postSort) + { + this.postSort = postSort; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("postId", getPostId()) + .append("postCode", getPostCode()) + .append("postName", getPostName()) + .append("postSort", getPostSort()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysRoleDept.java b/vetti-system/src/main/java/com/vetti/system/domain/SysRoleDept.java new file mode 100644 index 0000000..863ab85 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysRoleDept.java @@ -0,0 +1,46 @@ +package com.vetti.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和部门关联 sys_role_dept + * + * @author ruoyi + */ +public class SysRoleDept +{ + /** 角色ID */ + private Long roleId; + + /** 部门ID */ + private Long deptId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("deptId", getDeptId()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysRoleMenu.java b/vetti-system/src/main/java/com/vetti/system/domain/SysRoleMenu.java new file mode 100644 index 0000000..e81407f --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysRoleMenu.java @@ -0,0 +1,46 @@ +package com.vetti.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author ruoyi + */ +public class SysRoleMenu +{ + /** 角色ID */ + private Long roleId; + + /** 菜单ID */ + private Long menuId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("menuId", getMenuId()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysSerialNumberInfo.java b/vetti-system/src/main/java/com/vetti/system/domain/SysSerialNumberInfo.java new file mode 100644 index 0000000..04cf537 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysSerialNumberInfo.java @@ -0,0 +1,45 @@ +package com.vetti.system.domain; + +import lombok.Data; +import lombok.experimental.Accessors; +import io.swagger.annotations.ApiModelProperty; +import com.vetti.common.annotation.Excel; +import com.vetti.common.core.domain.BaseEntity; + +/** + * 系统流水号对象 sys_serial_number_info + * + * @author wangxiangshun + * @date 2025-09-10 + */ +@Data +@Accessors(chain = true) +public class SysSerialNumberInfo extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @ApiModelProperty("主键ID") + private Long id; + + /** 业务类型 */ + @ApiModelProperty("业务类型") + @Excel(name = "业务类型") + private String businessType; + + /** 编号前缀 */ + @ApiModelProperty("编号前缀") + @Excel(name = "编号前缀") + private String codePrefix; + + /** 流水号 */ + @ApiModelProperty("流水号") + @Excel(name = "流水号") + private String serialNumber; + + /** 锁 */ + @ApiModelProperty("锁") + @Excel(name = "锁") + private String lockFlag; + +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysUserOnline.java b/vetti-system/src/main/java/com/vetti/system/domain/SysUserOnline.java new file mode 100644 index 0000000..e8623ca --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysUserOnline.java @@ -0,0 +1,113 @@ +package com.vetti.system.domain; + +/** + * 当前在线会话 + * + * @author ruoyi + */ +public class SysUserOnline +{ + /** 会话编号 */ + private String tokenId; + + /** 部门名称 */ + private String deptName; + + /** 用户名称 */ + private String userName; + + /** 登录IP地址 */ + private String ipaddr; + + /** 登录地址 */ + private String loginLocation; + + /** 浏览器类型 */ + private String browser; + + /** 操作系统 */ + private String os; + + /** 登录时间 */ + private Long loginTime; + + public String getTokenId() + { + return tokenId; + } + + public void setTokenId(String tokenId) + { + this.tokenId = tokenId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysUserPost.java b/vetti-system/src/main/java/com/vetti/system/domain/SysUserPost.java new file mode 100644 index 0000000..b82237e --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysUserPost.java @@ -0,0 +1,46 @@ +package com.vetti.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 用户和岗位关联 sys_user_post + * + * @author ruoyi + */ +public class SysUserPost +{ + /** 用户ID */ + private Long userId; + + /** 岗位ID */ + private Long postId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("postId", getPostId()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/SysUserRole.java b/vetti-system/src/main/java/com/vetti/system/domain/SysUserRole.java new file mode 100644 index 0000000..74c053d --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/SysUserRole.java @@ -0,0 +1,46 @@ +package com.vetti.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 用户和角色关联 sys_user_role + * + * @author ruoyi + */ +public class SysUserRole +{ + /** 用户ID */ + private Long userId; + + /** 角色ID */ + private Long roleId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("roleId", getRoleId()) + .toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/vo/MetaVo.java b/vetti-system/src/main/java/com/vetti/system/domain/vo/MetaVo.java new file mode 100644 index 0000000..1338500 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/vo/MetaVo.java @@ -0,0 +1,108 @@ +package com.vetti.system.domain.vo; + +import com.vetti.common.international.International; +import com.vetti.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author ruoyi + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + @International(type = "sys_menu_name") + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo() + { + } + + public MetaVo(String title, String icon) + { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) + { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) + { + this.link = link; + } + } + + public boolean isNoCache() + { + return noCache; + } + + public void setNoCache(boolean noCache) + { + this.noCache = noCache; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public String getLink() + { + return link; + } + + public void setLink(String link) + { + this.link = link; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/domain/vo/RouterVo.java b/vetti-system/src/main/java/com/vetti/system/domain/vo/RouterVo.java new file mode 100644 index 0000000..d8ffa06 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/domain/vo/RouterVo.java @@ -0,0 +1,148 @@ +package com.vetti.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author ruoyi + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo +{ + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + public boolean getHidden() + { + return hidden; + } + + public void setHidden(boolean hidden) + { + this.hidden = hidden; + } + + public String getRedirect() + { + return redirect; + } + + public void setRedirect(String redirect) + { + this.redirect = redirect; + } + + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public Boolean getAlwaysShow() + { + return alwaysShow; + } + + public void setAlwaysShow(Boolean alwaysShow) + { + this.alwaysShow = alwaysShow; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysConfigMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..552f46c --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysConfigMapper.java @@ -0,0 +1,76 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysConfig; + +/** + * 参数配置 数据层 + * + * @author ruoyi + */ +public interface SysConfigMapper +{ + /** + * 查询参数配置信息 + * + * @param config 参数配置信息 + * @return 参数配置信息 + */ + public SysConfig selectConfig(SysConfig config); + + /** + * 通过ID查询配置 + * + * @param configId 参数ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数配置信息 + */ + public SysConfig checkConfigKeyUnique(String configKey); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 删除参数配置 + * + * @param configId 参数ID + * @return 结果 + */ + public int deleteConfigById(Long configId); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + * @return 结果 + */ + public int deleteConfigByIds(Long[] configIds); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysDeptMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..6b07374 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysDeptMapper.java @@ -0,0 +1,118 @@ +package com.vetti.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.vetti.common.core.domain.entity.SysDept; + +/** + * 部门管理 数据层 + * + * @author ruoyi + */ +public interface SysDeptMapper +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public int hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 + */ + public int checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param deptName 部门名称 + * @param parentId 父部门ID + * @return 结果 + */ + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); + + /** + * 新增部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + + /** + * 修改子元素关系 + * + * @param depts 子元素 + * @return 结果 + */ + public int updateDeptChildren(@Param("depts") List depts); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysDictDataMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..577bf2d --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysDictDataMapper.java @@ -0,0 +1,95 @@ +package com.vetti.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.vetti.common.core.domain.entity.SysDictData; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictDataMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据 + */ + public int countDictDataByType(String dictType); + + /** + * 通过字典ID删除字典数据信息 + * + * @param dictCode 字典数据ID + * @return 结果 + */ + public int deleteDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + * @return 结果 + */ + public int deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); + + /** + * 同步修改字典类型 + * + * @param oldDictType 旧字典类型 + * @param newDictType 新旧字典类型 + * @return 结果 + */ + public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysDictTypeMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..99fa001 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,83 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.common.core.domain.entity.SysDictType; + +/** + * 字典表 数据层 + * + * @author ruoyi + */ +public interface SysDictTypeMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 通过字典ID删除字典信息 + * + * @param dictId 字典ID + * @return 结果 + */ + public int deleteDictTypeById(Long dictId); + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + * @return 结果 + */ + public int deleteDictTypeByIds(Long[] dictIds); + + /** + * 新增字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public SysDictType checkDictTypeUnique(String dictType); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysInternationalInfoMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysInternationalInfoMapper.java new file mode 100644 index 0000000..92da2c6 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysInternationalInfoMapper.java @@ -0,0 +1,69 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysInternationalInfo; + +/** + * 数据国际化映射Mapper接口 + * + * @author wangxiangshun + * @date 2025-08-26 + */ +public interface SysInternationalInfoMapper +{ + /** + * 查询数据国际化映射 + * + * @param id 数据国际化映射主键 + * @return 数据国际化映射 + */ + public SysInternationalInfo selectSysInternationalInfoById(Long id); + + /** + * 查询数据国际化映射列表 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 数据国际化映射集合 + */ + public List selectSysInternationalInfoList(SysInternationalInfo sysInternationalInfo); + + /** + * 新增数据国际化映射 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 结果 + */ + public int insertSysInternationalInfo(SysInternationalInfo sysInternationalInfo); + + /** + * 修改数据国际化映射 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 结果 + */ + public int updateSysInternationalInfo(SysInternationalInfo sysInternationalInfo); + + /** + * 删除数据国际化映射 + * + * @param id 数据国际化映射主键 + * @return 结果 + */ + public int deleteSysInternationalInfoById(Long id); + + /** + * 批量删除数据国际化映射 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysInternationalInfoByIds(Long[] ids); + /** + * 批量新增数据国际化映射 + * + * @param sysInternationalInfoList 数据国际化映射列表 + * @return 结果 + */ + public int batchInsertSysInternationalInfo(List sysInternationalInfoList); + +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysLogininforMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..093544a --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysLogininforMapper.java @@ -0,0 +1,42 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 数据层 + * + * @author ruoyi + */ +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysMenuMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..465b6e5 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysMenuMapper.java @@ -0,0 +1,125 @@ +package com.vetti.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.vetti.common.core.domain.entity.SysMenu; + +/** + * 菜单表 数据层 + * + * @author ruoyi + */ +public interface SysMenuMapper +{ + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu); + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + public List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public List selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int hasChildByMenuId(Long menuId); + + /** + * 新增菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menuName 菜单名称 + * @param parentId 父菜单ID + * @return 结果 + */ + public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysNoticeMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..4c77310 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysNoticeMapper.java @@ -0,0 +1,60 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysNotice; + +/** + * 通知公告表 数据层 + * + * @author ruoyi + */ +public interface SysNoticeMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 批量删除公告 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysOperLogMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..a763c18 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysOperLogMapper.java @@ -0,0 +1,48 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysOperLog; + +/** + * 操作日志 数据层 + * + * @author ruoyi + */ +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysPostMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysPostMapper.java new file mode 100644 index 0000000..ad8d000 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysPostMapper.java @@ -0,0 +1,99 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysPost; + +/** + * 岗位信息 数据层 + * + * @author ruoyi + */ +public interface SysPostMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public List selectPostsByUserName(String userName); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 修改岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); + + /** + * 新增岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 校验岗位名称 + * + * @param postName 岗位名称 + * @return 结果 + */ + public SysPost checkPostNameUnique(String postName); + + /** + * 校验岗位编码 + * + * @param postCode 岗位编码 + * @return 结果 + */ + public SysPost checkPostCodeUnique(String postCode); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleDeptMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..18c342a --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,44 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author ruoyi + */ +public interface SysRoleDeptMapper +{ + /** + * 通过角色ID删除角色和部门关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleDeptByRoleId(Long roleId); + + /** + * 批量删除角色部门关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleDept(Long[] ids); + + /** + * 查询部门使用数量 + * + * @param deptId 部门ID + * @return 结果 + */ + public int selectCountRoleDeptByDeptId(Long deptId); + + /** + * 批量新增角色部门信息 + * + * @param roleDeptList 角色部门列表 + * @return 结果 + */ + public int batchRoleDept(List roleDeptList); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..50c71e8 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMapper.java @@ -0,0 +1,107 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.common.core.domain.entity.SysRole; + +/** + * 角色表 数据层 + * + * @author ruoyi + */ +public interface SysRoleMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List selectRolesByUserName(String userName); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); + + /** + * 修改角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 新增角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMenuMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..6181d73 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,44 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author ruoyi + */ +public interface SysRoleMenuMapper +{ + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(Long[] ids); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List roleMenuList); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysSerialNumberInfoMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysSerialNumberInfoMapper.java new file mode 100644 index 0000000..e11c7f2 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysSerialNumberInfoMapper.java @@ -0,0 +1,69 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysSerialNumberInfo; + +/** + * 系统流水号Mapper接口 + * + * @author wangxiangshun + * @date 2025-09-10 + */ +public interface SysSerialNumberInfoMapper +{ + /** + * 查询系统流水号 + * + * @param id 系统流水号主键 + * @return 系统流水号 + */ + public SysSerialNumberInfo selectSysSerialNumberInfoById(Long id); + + /** + * 查询系统流水号列表 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 系统流水号集合 + */ + public List selectSysSerialNumberInfoList(SysSerialNumberInfo sysSerialNumberInfo); + + /** + * 新增系统流水号 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 结果 + */ + public int insertSysSerialNumberInfo(SysSerialNumberInfo sysSerialNumberInfo); + + /** + * 修改系统流水号 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 结果 + */ + public int updateSysSerialNumberInfo(SysSerialNumberInfo sysSerialNumberInfo); + + /** + * 删除系统流水号 + * + * @param id 系统流水号主键 + * @return 结果 + */ + public int deleteSysSerialNumberInfoById(Long id); + + /** + * 批量删除系统流水号 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysSerialNumberInfoByIds(Long[] ids); + /** + * 批量新增系统流水号 + * + * @param sysSerialNumberInfoList 系统流水号列表 + * @return 结果 + */ + public int batchInsertSysSerialNumberInfo(List sysSerialNumberInfoList); + +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysUserMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..c60b90f --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysUserMapper.java @@ -0,0 +1,127 @@ +package com.vetti.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.vetti.common.core.domain.entity.SysUser; + +/** + * 用户表 数据层 + * + * @author ruoyi + */ +public interface SysUserMapper +{ + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar); + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userId") Long userId, @Param("password") String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public SysUser checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysUserPostMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysUserPostMapper.java new file mode 100644 index 0000000..56b4ede --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysUserPostMapper.java @@ -0,0 +1,44 @@ +package com.vetti.system.mapper; + +import java.util.List; +import com.vetti.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author ruoyi + */ +public interface SysUserPostMapper +{ + /** + * 通过用户ID删除用户和岗位关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserPostByUserId(Long userId); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 批量删除用户和岗位关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserPost(Long[] ids); + + /** + * 批量新增用户岗位信息 + * + * @param userPostList 用户岗位列表 + * @return 结果 + */ + public int batchUserPost(List userPostList); +} diff --git a/vetti-system/src/main/java/com/vetti/system/mapper/SysUserRoleMapper.java b/vetti-system/src/main/java/com/vetti/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..cb67f51 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,62 @@ +package com.vetti.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.vetti.system.domain.SysUserRole; + +/** + * 用户与角色关联表 数据层 + * + * @author ruoyi + */ +public interface SysUserRoleMapper +{ + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(Long[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List userRoleList); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteUserRoleInfo(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysConfigService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysConfigService.java new file mode 100644 index 0000000..d12ce9d --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysConfigService.java @@ -0,0 +1,89 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysConfig; + +/** + * 参数配置 服务层 + * + * @author ruoyi + */ +public interface ISysConfigService +{ + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + public String selectConfigByKey(String configKey); + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + public boolean selectCaptchaEnabled(); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + public void deleteConfigByIds(Long[] configIds); + + /** + * 加载参数缓存数据 + */ + public void loadingConfigCache(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + public boolean checkConfigKeyUnique(SysConfig config); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysDeptService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysDeptService.java new file mode 100644 index 0000000..f4d4301 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysDeptService.java @@ -0,0 +1,124 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.common.core.domain.TreeSelect; +import com.vetti.common.core.domain.entity.SysDept; + +/** + * 部门管理 服务层 + * + * @author ruoyi + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + public List selectDeptTreeList(SysDept dept); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + public List buildDeptTree(List depts); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + public List buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(Long roleId); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + public boolean checkDeptNameUnique(SysDept dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + public void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysDictDataService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysDictDataService.java new file mode 100644 index 0000000..621c507 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.common.core.domain.entity.SysDictData; + +/** + * 字典 业务层 + * + * @author ruoyi + */ +public interface ISysDictDataService +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(String dictType, String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + public void deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysDictTypeService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..e49e00b --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysDictTypeService.java @@ -0,0 +1,98 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.common.core.domain.entity.SysDictData; +import com.vetti.common.core.domain.entity.SysDictType; + +/** + * 字典 业务层 + * + * @author ruoyi + */ +public interface ISysDictTypeService +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 批量删除字典信息 + * + * @param dictIds 需要删除的字典ID + */ + public void deleteDictTypeByIds(Long[] dictIds); + + /** + * 加载字典缓存数据 + */ + public void loadingDictCache(); + + /** + * 清空字典缓存数据 + */ + public void clearDictCache(); + + /** + * 重置字典缓存数据 + */ + public void resetDictCache(); + + /** + * 新增保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public boolean checkDictTypeUnique(SysDictType dictType); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysInternationalInfoService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysInternationalInfoService.java new file mode 100644 index 0000000..6b83277 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysInternationalInfoService.java @@ -0,0 +1,70 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysInternationalInfo; + +/** + * 数据国际化映射Service接口 + * + * @author wangxiangshun + * @date 2025-08-26 + */ +public interface ISysInternationalInfoService +{ + /** + * 查询数据国际化映射 + * + * @param id 数据国际化映射主键 + * @return 数据国际化映射 + */ + public SysInternationalInfo selectSysInternationalInfoById(Long id); + + /** + * 查询数据国际化映射列表 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 数据国际化映射集合 + */ + public List selectSysInternationalInfoList(SysInternationalInfo sysInternationalInfo); + + /** + * 新增数据国际化映射 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 结果 + */ + public int insertSysInternationalInfo(SysInternationalInfo sysInternationalInfo); + + /** + * 修改数据国际化映射 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 结果 + */ + public int updateSysInternationalInfo(SysInternationalInfo sysInternationalInfo); + + /** + * 批量删除数据国际化映射 + * + * @param ids 需要删除的数据国际化映射主键集合 + * @return 结果 + */ + public int deleteSysInternationalInfoByIds(Long[] ids); + + /** + * 删除数据国际化映射信息 + * + * @param id 数据国际化映射主键 + * @return 结果 + */ + public int deleteSysInternationalInfoById(Long id); + + /** + * 批量新增数据国际化映射 + * + * @param sysInternationalInfoList 数据国际化映射列表 + * @return 结果 + */ + public int batchInsertSysInternationalInfo(List sysInternationalInfoList); + +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysLogininforService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysLogininforService.java new file mode 100644 index 0000000..948f5b8 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysLogininforService.java @@ -0,0 +1,40 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 服务层 + * + * @author ruoyi + */ +public interface ISysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysMenuService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysMenuService.java new file mode 100644 index 0000000..eaa27e7 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysMenuService.java @@ -0,0 +1,144 @@ +package com.vetti.system.service; + +import java.util.List; +import java.util.Set; +import com.vetti.common.core.domain.TreeSelect; +import com.vetti.common.core.domain.entity.SysMenu; +import com.vetti.system.domain.vo.RouterVo; + +/** + * 菜单 业务层 + * + * @author ruoyi + */ +public interface ISysMenuService +{ + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public Set selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(Long roleId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + public List buildMenus(List menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List buildMenuTree(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List buildMenuTreeSelect(List menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkMenuExistRole(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkMenuNameUnique(SysMenu menu); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysNoticeService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysNoticeService.java new file mode 100644 index 0000000..9f45ecb --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysNoticeService.java @@ -0,0 +1,60 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysNotice; + +/** + * 公告 服务层 + * + * @author ruoyi + */ +public interface ISysNoticeService +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 删除公告信息 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysOperLogService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysOperLogService.java new file mode 100644 index 0000000..47063d2 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysOperLogService.java @@ -0,0 +1,48 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysOperLog; + +/** + * 操作日志 服务层 + * + * @author ruoyi + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysPostService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysPostService.java new file mode 100644 index 0000000..f145f17 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysPostService.java @@ -0,0 +1,99 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysPost; + +/** + * 岗位信息 服务层 + * + * @author ruoyi + */ +public interface ISysPostService +{ + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位列表 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostCodeUnique(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysRoleService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysRoleService.java new file mode 100644 index 0000000..00c524c --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysRoleService.java @@ -0,0 +1,173 @@ +package com.vetti.system.service; + +import java.util.List; +import java.util.Set; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.system.domain.SysUserRole; + +/** + * 角色业务层 + * + * @author ruoyi + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleIds 角色id + */ + public void checkRoleDataScope(Long... roleIds); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRoleStatus(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + public int deleteAuthUsers(Long roleId, Long[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int insertAuthUsers(Long roleId, Long[] userIds); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysSerialNumberInfoService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysSerialNumberInfoService.java new file mode 100644 index 0000000..3cea37a --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysSerialNumberInfoService.java @@ -0,0 +1,81 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.system.domain.SysSerialNumberInfo; + +/** + * 系统流水号Service接口 + * + * @author wangxiangshun + * @date 2025-09-10 + */ +public interface ISysSerialNumberInfoService +{ + /** + * 查询系统流水号 + * + * @param id 系统流水号主键 + * @return 系统流水号 + */ + public SysSerialNumberInfo selectSysSerialNumberInfoById(Long id); + + /** + * 查询系统流水号列表 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 系统流水号集合 + */ + public List selectSysSerialNumberInfoList(SysSerialNumberInfo sysSerialNumberInfo); + + /** + * 新增系统流水号 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 结果 + */ + public int insertSysSerialNumberInfo(SysSerialNumberInfo sysSerialNumberInfo); + + /** + * 修改系统流水号 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 结果 + */ + public int updateSysSerialNumberInfo(SysSerialNumberInfo sysSerialNumberInfo); + + /** + * 批量删除系统流水号 + * + * @param ids 需要删除的系统流水号主键集合 + * @return 结果 + */ + public int deleteSysSerialNumberInfoByIds(Long[] ids); + + /** + * 删除系统流水号信息 + * + * @param id 系统流水号主键 + * @return 结果 + */ + public int deleteSysSerialNumberInfoById(Long id); + + /** + * 批量新增系统流水号 + * + * @param sysSerialNumberInfoList 系统流水号列表 + * @return 结果 + */ + public int batchInsertSysSerialNumberInfo(List sysSerialNumberInfoList); + + + /** + * 生成业务编号 + * + * @param codePrefix 业务编号前缀 + * @param businessType 业务类型 + * @param number 几位流水 + * @return + */ + String generateSerialNumber(String codePrefix, String businessType, String number); + +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysUserOnlineService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysUserOnlineService.java new file mode 100644 index 0000000..d40aea2 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysUserOnlineService.java @@ -0,0 +1,48 @@ +package com.vetti.system.service; + +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author ruoyi + */ +public interface ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user); + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + public SysUserOnline loginUserToUserOnline(LoginUser user); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/ISysUserService.java b/vetti-system/src/main/java/com/vetti/system/service/ISysUserService.java new file mode 100644 index 0000000..2e55f16 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/ISysUserService.java @@ -0,0 +1,206 @@ +package com.vetti.system.service; + +import java.util.List; +import com.vetti.common.core.domain.entity.SysUser; + +/** + * 用户 业务层 + * + * @author ruoyi + */ +public interface ISysUserService +{ + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserRoleGroup(String userName); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserPostGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkUserNameUnique(SysUser user); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkEmailUnique(SysUser user); + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + public void checkUserAllowed(SysUser user); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + public void checkUserDataScope(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + public boolean updateUserAvatar(Long userId, String avatar); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(Long userId, String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + public String importUser(List userList, Boolean isUpdateSupport, String operName); +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysConfigServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..56720c3 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,232 @@ +package com.vetti.system.service.impl; + +import java.util.Collection; +import java.util.List; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.common.annotation.DataSource; +import com.vetti.common.constant.CacheConstants; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.core.text.Convert; +import com.vetti.common.enums.DataSourceType; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.domain.SysConfig; +import com.vetti.system.mapper.SysConfigMapper; +import com.vetti.system.service.ISysConfigService; + +/** + * 参数配置 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysConfigServiceImpl implements ISysConfigService +{ + @Autowired + private SysConfigMapper configMapper; + + @Autowired + private RedisCache redisCache; + + /** + * 项目启动时,初始化参数到缓存 + */ + @PostConstruct + public void init() + { + loadingConfigCache(); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + @DataSource(DataSourceType.MASTER) + public SysConfig selectConfigById(Long configId) + { + SysConfig config = new SysConfig(); + config.setConfigId(configId); + return configMapper.selectConfig(config); + } + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数key + * @return 参数键值 + */ + @Override + public String selectConfigByKey(String configKey) + { + String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey))); + if (StringUtils.isNotEmpty(configValue)) + { + return configValue; + } + SysConfig config = new SysConfig(); + config.setConfigKey(configKey); + SysConfig retConfig = configMapper.selectConfig(config); + if (StringUtils.isNotNull(retConfig)) + { + redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + @Override + public boolean selectCaptchaEnabled() + { + String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); + if (StringUtils.isEmpty(captchaEnabled)) + { + return true; + } + return Convert.toBool(captchaEnabled); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List selectConfigList(SysConfig config) + { + return configMapper.selectConfigList(config); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int insertConfig(SysConfig config) + { + int row = configMapper.insertConfig(config); + if (row > 0) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int updateConfig(SysConfig config) + { + SysConfig temp = configMapper.selectConfigById(config.getConfigId()); + if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) + { + redisCache.deleteObject(getCacheKey(temp.getConfigKey())); + } + + int row = configMapper.updateConfig(config); + if (row > 0) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + @Override + public void deleteConfigByIds(Long[] configIds) + { + for (Long configId : configIds) + { + SysConfig config = selectConfigById(configId); + if (StringUtils.equals(UserConstants.YES, config.getConfigType())) + { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); + } + configMapper.deleteConfigById(configId); + redisCache.deleteObject(getCacheKey(config.getConfigKey())); + } + } + + /** + * 加载参数缓存数据 + */ + @Override + public void loadingConfigCache() + { + List configsList = configMapper.selectConfigList(new SysConfig()); + for (SysConfig config : configsList) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + Collection keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); + redisCache.deleteObject(keys); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() + { + clearConfigCache(); + loadingConfigCache(); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public boolean checkConfigKeyUnique(SysConfig config) + { + Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); + SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); + if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + private String getCacheKey(String configKey) + { + return CacheConstants.SYS_CONFIG_KEY + configKey; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysDeptServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..2660456 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,338 @@ +package com.vetti.system.service.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.common.annotation.DataScope; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.TreeSelect; +import com.vetti.common.core.domain.entity.SysDept; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.core.text.Convert; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.system.mapper.SysDeptMapper; +import com.vetti.system.mapper.SysRoleMapper; +import com.vetti.system.service.ISysDeptService; + +/** + * 部门管理 服务实现 + * + * @author ruoyi + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + @Override + public List selectDeptTreeList(SysDept dept) + { + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + return buildDeptTreeSelect(depts); + } + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + @Override + public List buildDeptTree(List depts) + { + List returnList = new ArrayList(); + List tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); + for (SysDept dept : depts) + { + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) + { + recursionFn(depts, dept); + returnList.add(dept); + } + } + if (returnList.isEmpty()) + { + returnList = depts; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List buildDeptTreeSelect(List depts) + { + List deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + @Override + public SysDept selectDeptById(Long deptId) + { + return deptMapper.selectDeptById(deptId); + } + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + @Override + public int selectNormalChildrenDeptById(Long deptId) + { + return deptMapper.selectNormalChildrenDeptById(deptId); + } + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public boolean hasChildByDeptId(Long deptId) + { + int result = deptMapper.hasChildByDeptId(deptId); + return result > 0; + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) + { + int result = deptMapper.checkDeptExistUser(deptId); + return result > 0; + } + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public boolean checkDeptNameUnique(SysDept dept) + { + Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId(); + SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId()); + if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + @Override + public void checkDeptDataScope(Long deptId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId()) && StringUtils.isNotNull(deptId)) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } + } + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int insertDept(SysDept dept) + { + SysDept info = deptMapper.selectDeptById(dept.getParentId()); + // 如果父节点不为正常状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) + { + throw new ServiceException("部门停用,不允许新增"); + } + dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); + return deptMapper.insertDept(dept); + } + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int updateDept(SysDept dept) + { + SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); + SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId()); + if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) + { + String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + int result = deptMapper.updateDept(dept); + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) + && !StringUtils.equals("0", dept.getAncestors())) + { + // 如果该部门是启用状态,则启用该部门的所有上级部门 + updateParentDeptStatusNormal(dept); + } + return result; + } + + /** + * 修改该部门的父级部门状态 + * + * @param dept 当前部门 + */ + private void updateParentDeptStatusNormal(SysDept dept) + { + String ancestors = dept.getAncestors(); + Long[] deptIds = Convert.toLongArray(ancestors); + deptMapper.updateDeptStatusNormal(deptIds); + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) + { + List children = deptMapper.selectChildrenDeptById(deptId); + for (SysDept child : children) + { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + } + if (children.size() > 0) + { + deptMapper.updateDeptChildren(children); + } + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public int deleteDeptById(Long deptId) + { + return deptMapper.deleteDeptById(deptId); + } + + /** + * 递归列表 + */ + private void recursionFn(List list, SysDept t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDept t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysDept n = (SysDept) it.next(); + if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysDept t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysDictDataServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..d6c9c88 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,111 @@ +package com.vetti.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.common.core.domain.entity.SysDictData; +import com.vetti.common.utils.DictUtils; +import com.vetti.system.mapper.SysDictDataMapper; +import com.vetti.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataList(SysDictData dictData) + { + return dictDataMapper.selectDictDataList(dictData); + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + @Override + public String selectDictLabel(String dictType, String dictValue) + { + return dictDataMapper.selectDictLabel(dictType, dictValue); + } + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + @Override + public SysDictData selectDictDataById(Long dictCode) + { + return dictDataMapper.selectDictDataById(dictCode); + } + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + @Override + public void deleteDictDataByIds(Long[] dictCodes) + { + for (Long dictCode : dictCodes) + { + SysDictData data = selectDictDataById(dictCode); + dictDataMapper.deleteDictDataById(dictCode); + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + } + + /** + * 新增保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int insertDictData(SysDictData data) + { + int row = dictDataMapper.insertDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } + + /** + * 修改保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int updateDictData(SysDictData data) + { + int row = dictDataMapper.updateDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysDictTypeServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..6e5fa8e --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,223 @@ +package com.vetti.system.service.impl; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysDictData; +import com.vetti.common.core.domain.entity.SysDictType; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.DictUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.mapper.SysDictDataMapper; +import com.vetti.system.mapper.SysDictTypeMapper; +import com.vetti.system.service.ISysDictTypeService; + +/** + * 字典 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysDictTypeServiceImpl implements ISysDictTypeService +{ + @Autowired + private SysDictTypeMapper dictTypeMapper; + + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 项目启动时,初始化字典到缓存 + */ + @PostConstruct + public void init() + { + loadingDictCache(); + } + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) + { + List dictDatas = DictUtils.getDictCache(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + return dictDatas; + } + dictDatas = dictDataMapper.selectDictDataByType(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + DictUtils.setDictCache(dictType, dictDatas); + return dictDatas; + } + return null; + } + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeById(Long dictId) + { + return dictTypeMapper.selectDictTypeById(dictId); + } + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeByType(String dictType) + { + return dictTypeMapper.selectDictTypeByType(dictType); + } + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + */ + @Override + public void deleteDictTypeByIds(Long[] dictIds) + { + for (Long dictId : dictIds) + { + SysDictType dictType = selectDictTypeById(dictId); + if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName())); + } + dictTypeMapper.deleteDictTypeById(dictId); + DictUtils.removeDictCache(dictType.getDictType()); + } + } + + /** + * 加载字典缓存数据 + */ + @Override + public void loadingDictCache() + { + SysDictData dictData = new SysDictData(); + dictData.setStatus("0"); + Map> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry> entry : dictDataMap.entrySet()) + { + DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList())); + } + } + + /** + * 清空字典缓存数据 + */ + @Override + public void clearDictCache() + { + DictUtils.clearDictCache(); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() + { + clearDictCache(); + loadingDictCache(); + } + + /** + * 新增保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + public int insertDictType(SysDictType dict) + { + int row = dictTypeMapper.insertDictType(dict); + if (row > 0) + { + DictUtils.setDictCache(dict.getDictType(), null); + } + return row; + } + + /** + * 修改保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + @Transactional + public int updateDictType(SysDictType dict) + { + SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId()); + dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType()); + int row = dictTypeMapper.updateDictType(dict); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); + DictUtils.setDictCache(dict.getDictType(), dictDatas); + } + return row; + } + + /** + * 校验字典类型称是否唯一 + * + * @param dict 字典类型 + * @return 结果 + */ + @Override + public boolean checkDictTypeUnique(SysDictType dict) + { + Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId(); + SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType()); + if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysInternationalInfoServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysInternationalInfoServiceImpl.java new file mode 100644 index 0000000..03b080e --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysInternationalInfoServiceImpl.java @@ -0,0 +1,118 @@ +package com.vetti.system.service.impl; + +import java.util.List; + +import com.vetti.common.core.service.BaseServiceImpl; +import com.vetti.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.vetti.system.mapper.SysInternationalInfoMapper; +import com.vetti.system.domain.SysInternationalInfo; +import com.vetti.system.service.ISysInternationalInfoService; + +/** + * 数据国际化映射Service业务层处理 + * + * @author wangxiangshun + * @date 2025-08-26 + */ +@SuppressWarnings("all") +@Service +public class SysInternationalInfoServiceImpl extends BaseServiceImpl implements ISysInternationalInfoService +{ + @Autowired + private SysInternationalInfoMapper sysInternationalInfoMapper; + + /** + * 查询数据国际化映射 + * + * @param id 数据国际化映射主键 + * @return 数据国际化映射 + */ + @Transactional(readOnly = true) + @Override + public SysInternationalInfo selectSysInternationalInfoById(Long id) + { + return sysInternationalInfoMapper.selectSysInternationalInfoById(id); + } + + /** + * 查询数据国际化映射列表 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 数据国际化映射 + */ + @Transactional(readOnly = true) + @Override + public List selectSysInternationalInfoList(SysInternationalInfo sysInternationalInfo) + { + return sysInternationalInfoMapper.selectSysInternationalInfoList(sysInternationalInfo); + } + + /** + * 新增数据国际化映射 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int insertSysInternationalInfo(SysInternationalInfo sysInternationalInfo) + { + sysInternationalInfo.setCreateTime(DateUtils.getNowDate()); + return sysInternationalInfoMapper.insertSysInternationalInfo(sysInternationalInfo); + } + + /** + * 修改数据国际化映射 + * + * @param sysInternationalInfo 数据国际化映射 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int updateSysInternationalInfo(SysInternationalInfo sysInternationalInfo) + { + sysInternationalInfo.setUpdateTime(DateUtils.getNowDate()); + return sysInternationalInfoMapper.updateSysInternationalInfo(sysInternationalInfo); + } + + /** + * 批量删除数据国际化映射 + * + * @param ids 需要删除的数据国际化映射主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int deleteSysInternationalInfoByIds(Long[] ids) + { + return sysInternationalInfoMapper.deleteSysInternationalInfoByIds(ids); + } + + /** + * 删除数据国际化映射信息 + * + * @param id 数据国际化映射主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int deleteSysInternationalInfoById(Long id) + { + return sysInternationalInfoMapper.deleteSysInternationalInfoById(id); + } + /** + * 批量新增数据国际化映射 + * + * @param sysInternationalInfoList 数据国际化映射列表 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int batchInsertSysInternationalInfo(List sysInternationalInfoList){ + return sysInternationalInfoMapper.batchInsertSysInternationalInfo(sysInternationalInfoList); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysLogininforServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..79c7363 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,65 @@ +package com.vetti.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.system.domain.SysLogininfor; +import com.vetti.system.mapper.SysLogininforMapper; +import com.vetti.system.service.ISysLogininforService; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysLogininforServiceImpl implements ISysLogininforService +{ + + @Autowired + private SysLogininforMapper logininforMapper; + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininfor logininfor) + { + logininforMapper.insertLogininfor(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininfor logininfor) + { + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) + { + return logininforMapper.deleteLogininforByIds(infoIds); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysMenuServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..b329d38 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,543 @@ +package com.vetti.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.common.constant.Constants; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.TreeSelect; +import com.vetti.common.core.domain.entity.SysMenu; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.domain.vo.MetaVo; +import com.vetti.system.domain.vo.RouterVo; +import com.vetti.system.mapper.SysMenuMapper; +import com.vetti.system.mapper.SysRoleMapper; +import com.vetti.system.mapper.SysRoleMenuMapper; +import com.vetti.system.service.ISysMenuService; + +/** + * 菜单 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysMenuServiceImpl implements ISysMenuService +{ + public static final String PREMISSION_STRING = "perms[\"{0}\"]"; + + @Autowired + private SysMenuMapper menuMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Override + public List selectMenuList(Long userId) + { + return selectMenuList(new SysMenu(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List selectMenuList(SysMenu menu, Long userId) + { + List menuList = null; + // 管理员显示所有菜单信息 + if (SysUser.isAdmin(userId)) + { + menuList = menuMapper.selectMenuList(menu); + } + else + { + menu.getParams().put("userId", userId); + menuList = menuMapper.selectMenuListByUserId(menu); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByUserId(Long userId) + { + List perms = menuMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByRoleId(Long roleId) + { + List perms = menuMapper.selectMenuPermsByRoleId(roleId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户名称 + * @return 菜单列表 + */ + @Override + public List selectMenuTreeByUserId(Long userId) + { + List menus = null; + if (SecurityUtils.isAdmin(userId)) + { + menus = menuMapper.selectMenuTreeAll(); + } + else + { + menus = menuMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, 0); + } + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List buildMenus(List menus) + { + List routers = new LinkedList(); + for (SysMenu menu : menus) + { + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setQuery(menu.getQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + List cMenus = menu.getChildren(); + if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) + { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } + else if (isMenuFrame(menu)) + { + router.setMeta(null); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(getRouteName(menu.getRouteName(), menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } + else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) + { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(getRouteName(menu.getRouteName(), routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + @Override + public List buildMenuTree(List menus) + { + List returnList = new ArrayList(); + List tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + for (Iterator iterator = menus.iterator(); iterator.hasNext();) + { + SysMenu menu = (SysMenu) iterator.next(); + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(menu.getParentId())) + { + recursionFn(menus, menu); + returnList.add(menu); + } + } + if (returnList.isEmpty()) + { + returnList = menus; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + @Override + public List buildMenuTreeSelect(List menus) + { + List menuTrees = buildMenuTree(menus); + return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenu selectMenuById(Long menuId) + { + return menuMapper.selectMenuById(menuId); + } + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean hasChildByMenuId(Long menuId) + { + int result = menuMapper.hasChildByMenuId(menuId); + return result > 0; + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) + { + int result = roleMenuMapper.checkMenuExistRole(menuId); + return result > 0; + } + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int insertMenu(SysMenu menu) + { + return menuMapper.insertMenu(menu); + } + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int updateMenu(SysMenu menu) + { + return menuMapper.updateMenu(menu); + } + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) + { + return menuMapper.deleteMenuById(menuId); + } + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkMenuNameUnique(SysMenu menu) + { + Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); + SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId()); + if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取路由名称 + * + * @param menu 菜单信息 + * @return 路由名称 + */ + public String getRouteName(SysMenu menu) + { + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) + { + return StringUtils.EMPTY; + } + return getRouteName(menu.getRouteName(), menu.getPath()); + } + + /** + * 获取路由名称,如没有配置路由名称则取路由地址 + * + * @param name 路由名称 + * @param path 路由地址 + * @return 路由名称(驼峰格式) + */ + public String getRouteName(String name, String path) + { + String routerName = StringUtils.isNotEmpty(name) ? name : path; + return StringUtils.capitalize(routerName); + } + + /** + * 获取路由地址 + * + * @param menu 菜单信息 + * @return 路由地址 + */ + public String getRouterPath(SysMenu menu) + { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) + { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) + { + routerPath = "/" + menu.getPath(); + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame(menu)) + { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenu menu) + { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) + { + component = menu.getComponent(); + } + else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) + { + component = UserConstants.INNER_LINK; + } + else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) + { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isMenuFrame(SysMenu menu) + { + return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isInnerLink(SysMenu menu) + { + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); + } + + /** + * 是否为parent_view组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isParentView(SysMenu menu) + { + return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + public List getChildPerms(List list, int parentId) + { + List returnList = new ArrayList(); + for (Iterator iterator = list.iterator(); iterator.hasNext();) + { + SysMenu t = (SysMenu) iterator.next(); + // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 + if (t.getParentId() == parentId) + { + recursionFn(list, t); + returnList.add(t); + } + } + return returnList; + } + + /** + * 递归列表 + * + * @param list 分类表 + * @param t 子节点 + */ + private void recursionFn(List list, SysMenu t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenu t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysMenu n = (SysMenu) it.next(); + if (n.getParentId().longValue() == t.getMenuId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysMenu t) + { + return getChildList(list, t).size() > 0; + } + + /** + * 内链域名特殊字符替换 + * + * @return 替换后的内链域名 + */ + public String innerLinkReplaceEach(String path) + { + return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":" }, + new String[] { "", "", "", "/", "/" }); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysNoticeServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..048c75c --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,92 @@ +package com.vetti.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.system.domain.SysNotice; +import com.vetti.system.mapper.SysNoticeMapper; +import com.vetti.system.service.ISysNoticeService; + +/** + * 公告 服务层实现 + * + * @author ruoyi + */ +@Service +public class SysNoticeServiceImpl implements ISysNoticeService +{ + @Autowired + private SysNoticeMapper noticeMapper; + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + @Override + public SysNotice selectNoticeById(Long noticeId) + { + return noticeMapper.selectNoticeById(noticeId); + } + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + @Override + public List selectNoticeList(SysNotice notice) + { + return noticeMapper.selectNoticeList(notice); + } + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int insertNotice(SysNotice notice) + { + return noticeMapper.insertNotice(notice); + } + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int updateNotice(SysNotice notice) + { + return noticeMapper.updateNotice(notice); + } + + /** + * 删除公告对象 + * + * @param noticeId 公告ID + * @return 结果 + */ + @Override + public int deleteNoticeById(Long noticeId) + { + return noticeMapper.deleteNoticeById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) + { + return noticeMapper.deleteNoticeByIds(noticeIds); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysOperLogServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..48d37bf --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,76 @@ +package com.vetti.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.system.domain.SysOperLog; +import com.vetti.system.mapper.SysOperLogMapper; +import com.vetti.system.service.ISysOperLogService; + +/** + * 操作日志 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysOperLogServiceImpl implements ISysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) + { + operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) + { + return operLogMapper.deleteOperLogByIds(operIds); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysPostServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..d4e859a --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,178 @@ +package com.vetti.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.domain.SysPost; +import com.vetti.system.mapper.SysPostMapper; +import com.vetti.system.mapper.SysUserPostMapper; +import com.vetti.system.service.ISysPostService; + +/** + * 岗位信息 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysPostServiceImpl implements ISysPostService +{ + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List selectPostAll() + { + return postMapper.selectPostAll(); + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPost selectPostById(Long postId) + { + return postMapper.selectPostById(postId); + } + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + @Override + public List selectPostListByUserId(Long userId) + { + return postMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostNameUnique(post.getPostName()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验岗位编码是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostCodeUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostCodeUnique(post.getPostCode()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) + { + return postMapper.deletePostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) + { + for (Long postId : postIds) + { + SysPost post = selectPostById(postId); + if (countUserPostById(postId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName())); + } + } + return postMapper.deletePostByIds(postIds); + } + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int insertPost(SysPost post) + { + return postMapper.insertPost(post); + } + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int updatePost(SysPost post) + { + return postMapper.updatePost(post); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysRoleServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..4841fb1 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,427 @@ +package com.vetti.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.vetti.common.annotation.DataScope; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.system.domain.SysRoleDept; +import com.vetti.system.domain.SysRoleMenu; +import com.vetti.system.domain.SysUserRole; +import com.vetti.system.mapper.SysRoleDeptMapper; +import com.vetti.system.mapper.SysRoleMapper; +import com.vetti.system.mapper.SysRoleMenuMapper; +import com.vetti.system.mapper.SysUserRoleMapper; +import com.vetti.system.service.ISysRoleService; + +/** + * 角色 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysRoleServiceImpl implements ISysRoleService +{ + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleDeptMapper roleDeptMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolePermissionByUserId(userId); + List roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) + { + return roleMapper.selectRoleListByUserId(userId); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRole selectRoleById(Long roleId) + { + return roleMapper.selectRoleById(roleId); + } + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleNameUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleKeyUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + @Override + public void checkRoleAllowed(SysRole role) + { + if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleIds 角色id + */ + @Override + public void checkRoleDataScope(Long... roleIds) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + for (Long roleId : roleIds) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List roles = SpringUtils.getAopProxy(this).selectRoleList(role); + if (StringUtils.isEmpty(roles)) + { + throw new ServiceException("没有权限访问角色数据!"); + } + } + } + } + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + public int countUserRoleByRoleId(Long roleId) + { + return userRoleMapper.countUserRoleByRoleId(roleId); + } + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int insertRole(SysRole role) + { + // 新增角色信息 + roleMapper.insertRole(role); + return insertRoleMenu(role); + } + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int updateRole(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); + return insertRoleMenu(role); + } + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int updateRoleStatus(SysRole role) + { + return roleMapper.updateRole(role); + } + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int authDataScope(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); + // 新增角色和部门信息(数据权限) + return insertRoleDept(role); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + public int insertRoleMenu(SysRole role) + { + int rows = 1; + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long menuId : role.getMenuIds()) + { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (list.size() > 0) + { + rows = roleMenuMapper.batchRoleMenu(list); + } + return rows; + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + public int insertRoleDept(SysRole role) + { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList(); + for (Long deptId : role.getDeptIds()) + { + SysRoleDept rd = new SysRoleDept(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (list.size() > 0) + { + rows = roleDeptMapper.batchRoleDept(list); + } + return rows; + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleById(Long roleId) + { + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId); + } + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleByIds(Long[] roleIds) + { + for (Long roleId : roleIds) + { + checkRoleAllowed(new SysRole(roleId)); + checkRoleDataScope(roleId); + SysRole role = selectRoleById(roleId); + if (countUserRoleByRoleId(roleId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDept(roleIds); + return roleMapper.deleteRoleByIds(roleIds); + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRole userRole) + { + return userRoleMapper.deleteUserRoleInfo(userRole); + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, Long[] userIds) + { + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, Long[] userIds) + { + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long userId : userIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysSerialNumberInfoServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysSerialNumberInfoServiceImpl.java new file mode 100644 index 0000000..59ef4e9 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysSerialNumberInfoServiceImpl.java @@ -0,0 +1,179 @@ +package com.vetti.system.service.impl; + +import java.util.List; + +import cn.hutool.core.collection.CollectionUtil; +import com.vetti.common.core.service.BaseServiceImpl; +import com.vetti.common.enums.FillTypeEnum; +import com.vetti.common.utils.DateUtils; +import com.vetti.common.utils.bean.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.vetti.system.mapper.SysSerialNumberInfoMapper; +import com.vetti.system.domain.SysSerialNumberInfo; +import com.vetti.system.service.ISysSerialNumberInfoService; + +/** + * 系统流水号Service业务层处理 + * + * @author wangxiangshun + * @date 2025-09-10 + */ +@SuppressWarnings("all") +@Service +public class SysSerialNumberInfoServiceImpl extends BaseServiceImpl implements ISysSerialNumberInfoService +{ + @Autowired + private SysSerialNumberInfoMapper sysSerialNumberInfoMapper; + + /** + * 查询系统流水号 + * + * @param id 系统流水号主键 + * @return 系统流水号 + */ + @Transactional(readOnly = true) + @Override + public SysSerialNumberInfo selectSysSerialNumberInfoById(Long id) + { + return sysSerialNumberInfoMapper.selectSysSerialNumberInfoById(id); + } + + /** + * 查询系统流水号列表 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 系统流水号 + */ + @Transactional(readOnly = true) + @Override + public List selectSysSerialNumberInfoList(SysSerialNumberInfo sysSerialNumberInfo) + { + return sysSerialNumberInfoMapper.selectSysSerialNumberInfoList(sysSerialNumberInfo); + } + + /** + * 新增系统流水号 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int insertSysSerialNumberInfo(SysSerialNumberInfo sysSerialNumberInfo) + { + sysSerialNumberInfo.setCreateTime(DateUtils.getNowDate()); + return sysSerialNumberInfoMapper.insertSysSerialNumberInfo(sysSerialNumberInfo); + } + + /** + * 修改系统流水号 + * + * @param sysSerialNumberInfo 系统流水号 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int updateSysSerialNumberInfo(SysSerialNumberInfo sysSerialNumberInfo) + { + sysSerialNumberInfo.setUpdateTime(DateUtils.getNowDate()); + return sysSerialNumberInfoMapper.updateSysSerialNumberInfo(sysSerialNumberInfo); + } + + /** + * 批量删除系统流水号 + * + * @param ids 需要删除的系统流水号主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int deleteSysSerialNumberInfoByIds(Long[] ids) + { + return sysSerialNumberInfoMapper.deleteSysSerialNumberInfoByIds(ids); + } + + /** + * 删除系统流水号信息 + * + * @param id 系统流水号主键 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int deleteSysSerialNumberInfoById(Long id) + { + return sysSerialNumberInfoMapper.deleteSysSerialNumberInfoById(id); + } + /** + * 批量新增系统流水号 + * + * @param sysSerialNumberInfoList 系统流水号列表 + * @return 结果 + */ + @Transactional(rollbackFor=Exception.class) + @Override + public int batchInsertSysSerialNumberInfo(List sysSerialNumberInfoList){ + return sysSerialNumberInfoMapper.batchInsertSysSerialNumberInfo(sysSerialNumberInfoList); + } + + /** + * 生成业务编号 + * @param codePrefix 业务编号前缀 + * @param businessType 业务类型 + * @param number 几位流水 + * @return + */ + @Override + public String generateSerialNumber(String codePrefix, String businessType, String number) { + + String code = ""; + Integer serialNumber = 1; + SysSerialNumberInfo sysSerialNumberInfo = new SysSerialNumberInfo(); + sysSerialNumberInfo.setCodePrefix(codePrefix); + sysSerialNumberInfo.setBusinessType(businessType); + List sysSerialNumberInfoList = selectSysSerialNumberInfoList(sysSerialNumberInfo); + SysSerialNumberInfo xtSerialNumberInfoQueryVo = null; + if(CollectionUtil.isNotEmpty(sysSerialNumberInfoList)){ + xtSerialNumberInfoQueryVo = sysSerialNumberInfoList.get(0); + } + if (xtSerialNumberInfoQueryVo != null) { + if ("0".equals(xtSerialNumberInfoQueryVo.getLockFlag())) { + SysSerialNumberInfo serialNumberInfo = new SysSerialNumberInfo(); + BeanUtils.copyProperties(xtSerialNumberInfoQueryVo, serialNumberInfo); + serialNumberInfo.setLockFlag("1"); + // 进行上锁 + updateSysSerialNumberInfo(serialNumberInfo); + // 取得流水号进行编号生成 + serialNumber = Integer.parseInt(serialNumberInfo.getSerialNumber()) + 1; + String serialNumberStr = String.format("%0" + number + "d", serialNumber); + // 释放锁,更新流水号-获取最新数据 + serialNumberInfo.setLockFlag("0"); + serialNumberInfo.setSerialNumber(serialNumberStr); + // 进行释放 + updateSysSerialNumberInfo(serialNumberInfo); + code = codePrefix + serialNumberStr; + } else {// 上锁 + try { + Thread.sleep(500L); + generateSerialNumber(codePrefix, businessType, number); + } catch (Exception e) { + throw new IllegalArgumentException("获取编号失败!"); + } + } + } else { + SysSerialNumberInfo serialNumberInfo = new SysSerialNumberInfo(); + String serialNumberStr = String.format("%0" + number + "d", 1); + serialNumberInfo.setLockFlag("0"); + serialNumberInfo.setSerialNumber(serialNumberStr); + serialNumberInfo.setCodePrefix(codePrefix); + serialNumberInfo.setBusinessType(businessType); + fill(FillTypeEnum.INSERT.getCode(), serialNumberInfo); + insertSysSerialNumberInfo(serialNumberInfo); + code = codePrefix + serialNumberStr; + } + return code; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysUserOnlineServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 0000000..a4d0099 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,96 @@ +package com.vetti.system.service.impl; + +import org.springframework.stereotype.Service; +import com.vetti.common.core.domain.model.LoginUser; +import com.vetti.common.utils.StringUtils; +import com.vetti.system.domain.SysUserOnline; +import com.vetti.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user) + { + if (StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + @Override + public SysUserOnline loginUserToUserOnline(LoginUser user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) + { + return null; + } + SysUserOnline sysUserOnline = new SysUserOnline(); + sysUserOnline.setTokenId(user.getToken()); + sysUserOnline.setUserName(user.getUsername()); + sysUserOnline.setIpaddr(user.getIpaddr()); + sysUserOnline.setLoginLocation(user.getLoginLocation()); + sysUserOnline.setBrowser(user.getBrowser()); + sysUserOnline.setOs(user.getOs()); + sysUserOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) + { + sysUserOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return sysUserOnline; + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/SysUserServiceImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..69b2dcc --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,552 @@ +package com.vetti.system.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.validation.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import com.vetti.common.annotation.DataScope; +import com.vetti.common.constant.UserConstants; +import com.vetti.common.core.domain.entity.SysRole; +import com.vetti.common.core.domain.entity.SysUser; +import com.vetti.common.exception.ServiceException; +import com.vetti.common.utils.SecurityUtils; +import com.vetti.common.utils.StringUtils; +import com.vetti.common.utils.bean.BeanValidators; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.system.domain.SysPost; +import com.vetti.system.domain.SysUserPost; +import com.vetti.system.domain.SysUserRole; +import com.vetti.system.mapper.SysPostMapper; +import com.vetti.system.mapper.SysRoleMapper; +import com.vetti.system.mapper.SysUserMapper; +import com.vetti.system.mapper.SysUserPostMapper; +import com.vetti.system.mapper.SysUserRoleMapper; +import com.vetti.system.service.ISysConfigService; +import com.vetti.system.service.ISysDeptService; +import com.vetti.system.service.ISysUserService; + +/** + * 用户 业务层处理 + * + * @author ruoyi + */ +@Service +public class SysUserServiceImpl implements ISysUserService +{ + private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + @Autowired + private ISysConfigService configService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectAllocatedList(SysUser user) + { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUnallocatedList(SysUser user) + { + return userMapper.selectUnallocatedList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByUserName(String userName) + { + return userMapper.selectUserByUserName(userName); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) + { + return userMapper.selectUserById(userId); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) + { + List list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserPostGroup(String userName) + { + List list = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkUserNameUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkUserNameUnique(user.getUserName()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkPhoneUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkEmailUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkEmailUnique(user.getEmail()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + @Override + public void checkUserAllowed(SysUser user) + { + if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysUser user = new SysUser(); + user.setUserId(userId); + List users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) + { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int insertUser(SysUser user) + { + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserPost(user); + // 新增用户与角色管理 + insertUserRole(user); + //如果是车队管理员并且还是司机,就要往司机表中添加一条数据用做关联 + + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) + { + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) + { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRole(user); + // 删除用户与岗位关联 + userPostMapper.deleteUserPostByUserId(userId); + // 新增用户与岗位管理 + insertUserPost(user); + return userMapper.updateUser(user); + } + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + @Override + @Transactional + public void insertUserAuth(Long userId, Long[] roleIds) + { + userRoleMapper.deleteUserRoleByUserId(userId); + insertUserRole(userId, roleIds); + } + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserStatus(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(Long userId, String avatar) + { + return userMapper.updateUserAvatar(userId, avatar) > 0; + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(Long userId, String password) + { + return userMapper.resetUserPwd(userId, password); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) + { + this.insertUserRole(user.getUserId(), user.getRoleIds()); + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) + { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotEmpty(posts)) + { + // 新增用户与岗位管理 + List list = new ArrayList(posts.length); + for (Long postId : posts) + { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + userPostMapper.batchUserPost(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) + { + if (StringUtils.isNotEmpty(roleIds)) + { + // 新增用户与角色管理 + List list = new ArrayList(roleIds.length); + for (Long roleId : roleIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + userRoleMapper.batchUserRole(list); + } + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserById(Long userId) + { + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(Long[] userIds) + { + for (Long userId : userIds) + { + checkUserAllowed(new SysUser(userId)); + checkUserDataScope(userId); + } + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 + userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + @Override + public String importUser(List userList, Boolean isUpdateSupport, String operName) + { + if (StringUtils.isNull(userList) || userList.size() == 0) + { + throw new ServiceException("导入用户数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + for (SysUser user : userList) + { + try + { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByUserName(user.getUserName()); + if (StringUtils.isNull(u)) + { + BeanValidators.validateWithException(validator, user); + deptService.checkDeptDataScope(user.getDeptId()); + String password = configService.selectConfigByKey("sys.user.initPassword"); + user.setPassword(SecurityUtils.encryptPassword(password)); + user.setCreateBy(operName); + userMapper.insertUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 导入成功"); + } + else if (isUpdateSupport) + { + BeanValidators.validateWithException(validator, user); + checkUserAllowed(u); + checkUserDataScope(u.getUserId()); + deptService.checkDeptDataScope(user.getDeptId()); + user.setUserId(u.getUserId()); + user.setUpdateBy(operName); + userMapper.updateUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + log.error(msg, e); + } + } + if (failureNum > 0) + { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } + else + { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } +} diff --git a/vetti-system/src/main/java/com/vetti/system/service/impl/international/InternationalResourceGetterImpl.java b/vetti-system/src/main/java/com/vetti/system/service/impl/international/InternationalResourceGetterImpl.java new file mode 100644 index 0000000..c810fe2 --- /dev/null +++ b/vetti-system/src/main/java/com/vetti/system/service/impl/international/InternationalResourceGetterImpl.java @@ -0,0 +1,87 @@ +package com.vetti.system.service.impl.international; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.vetti.common.core.redis.RedisCache; +import com.vetti.common.enums.InternationalLangTypeEnum; +import com.vetti.common.international.InternationalResourceGetter; +import com.vetti.common.utils.spring.SpringUtils; +import com.vetti.system.domain.SysInternationalInfo; +import com.vetti.system.service.ISysInternationalInfoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 国际化-获取翻译值 服务实现类 + * + * @author WangXiangShun + * @since 2025-08-28 + */ +@Slf4j +@Service +public class InternationalResourceGetterImpl implements InternationalResourceGetter { + + private ISysInternationalInfoService internationalInfoService; + + private RedisCache redisCache; + + /** + * 实例化对象 + */ + public void init() { + if (internationalInfoService == null) { + internationalInfoService = SpringUtils.getBean(ISysInternationalInfoService.class); + } + if (redisCache == null){ + redisCache = SpringUtils.getBean(RedisCache.class); + } + } + + /** + * 如果是中文,直接返回国际化主表中的资源名称 如果是其他语言,根据语言类型,去国际化详细表中取对应的数据 + * + * @param resourceId + * 资源ID + * @param lang + * 语言 + * @return + */ + @Override + public String getText(String resourceId, String lang) { + init(); + String internationalValue = ""; + //查询所有国际化语言列表信息 + //根据资源ID查询对应的国际化信息 + //先根据resourceId去redis查询,如果不存在再去数据库中查询 + String str = redisCache.getCacheObject(resourceId); + SysInternationalInfo internationalInfo = null; + if(StrUtil.isNotEmpty(str)){ + internationalInfo = JSONUtil.toBean(str,SysInternationalInfo.class); + }else{ + SysInternationalInfo query = new SysInternationalInfo(); + query.setResourceId(resourceId); + List internationalInfoList = internationalInfoService.selectSysInternationalInfoList(query); + if(CollectionUtil.isNotEmpty(internationalInfoList)){ + internationalInfo = internationalInfoList.get(0); + redisCache.setCacheObject(resourceId,JSONUtil.toJsonStr(internationalInfo),10, TimeUnit.MINUTES); + } + } + if(internationalInfo != null){ + if(InternationalLangTypeEnum.INTERNATIONAL_LANG_TYPE_ENUM_ZH.getCode().equals(lang)){ + internationalValue = internationalInfo.getResourceZhText(); + }else if(InternationalLangTypeEnum.INTERNATIONAL_LANG_TYPE_ENUM_EN.getCode().equals(lang)){ + internationalValue = internationalInfo.getResourceEnText(); + } + }else{ + if(internationalInfo == null){ + internationalValue = resourceId; + } + } + return internationalValue; + } + +} diff --git a/vetti-system/src/main/resources/mapper/system/SysConfigMapper.xml b/vetti-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..f48d16d --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysDeptMapper.xml b/vetti-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..763661c --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + sysdate() + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/vetti-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..6ab9190 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark + from sys_dict_data + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/vetti-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..95de5fb --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysInternationalInfoMapper.xml b/vetti-system/src/main/resources/mapper/system/SysInternationalInfoMapper.xml new file mode 100644 index 0000000..6cc3956 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysInternationalInfoMapper.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + select id, resource_id, resource_type, resource_zh_text, resource_en_text, resource_ja_text, remark, del_flag, create_by, create_time, update_by, update_time, version_no from sys_international_info + + + + + + + + insert into sys_international_info + + resource_id, + resource_type, + resource_zh_text, + resource_en_text, + resource_ja_text, + remark, + del_flag, + create_by, + create_time, + update_by, + update_time, + version_no, + + + #{resourceId}, + #{resourceType}, + #{resourceZhText}, + #{resourceEnText}, + #{resourceJaText}, + #{remark}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{versionNo}, + + + + + update sys_international_info + + resource_id = #{resourceId}, + resource_type = #{resourceType}, + resource_zh_text = #{resourceZhText}, + resource_en_text = #{resourceEnText}, + resource_ja_text = #{resourceJaText}, + remark = #{remark}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + version_no = #{versionNo}, + + where id = #{id} + + + + delete from sys_international_info where id = #{id} + + + + delete from sys_international_info where id in + + #{id} + + + + + insert into sys_international_info( id, resource_id, resource_type, resource_zh_text, resource_en_text, resource_ja_text, remark, del_flag, create_by, create_time, update_by, update_time, version_no) values + + ( #{item.id}, #{item.resourceId}, #{item.resourceType}, #{item.resourceZhText}, #{item.resourceEnText}, #{item.resourceJaText}, #{item.remark}, #{item.delFlag}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.versionNo}) + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/vetti-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..645c696 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysMenuMapper.xml b/vetti-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..38c8589 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, path, component, `query`, route_name, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + path = #{path}, + component = #{component}, + `query` = #{query}, + route_name = #{routeName}, + is_frame = #{isFrame}, + is_cache = #{isCache}, + menu_type = #{menuType}, + visible = #{visible}, + status = #{status}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + path, + component, + `query`, + route_name, + is_frame, + is_cache, + menu_type, + visible, + status, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{path}, + #{component}, + #{query}, + #{routeName}, + #{isFrame}, + #{isCache}, + #{menuType}, + #{visible}, + #{status}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_menu where menu_id = #{menuId} + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/vetti-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..df3e580 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark + from sys_notice + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/vetti-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..c272494 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysPostMapper.xml b/vetti-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..94367dd --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/vetti-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..d29781c --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysRoleMapper.xml b/vetti-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..1f59b29 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + + + + + + + + + + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/vetti-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..0f311d1 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysSerialNumberInfoMapper.xml b/vetti-system/src/main/resources/mapper/system/SysSerialNumberInfoMapper.xml new file mode 100644 index 0000000..aae3210 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysSerialNumberInfoMapper.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + select id, business_type, code_prefix, serial_number, lock_flag, create_by, create_time, update_by, update_time, remark from sys_serial_number_info + + + + + + + + insert into sys_serial_number_info + + business_type, + code_prefix, + serial_number, + lock_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{businessType}, + #{codePrefix}, + #{serialNumber}, + #{lockFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update sys_serial_number_info + + business_type = #{businessType}, + code_prefix = #{codePrefix}, + serial_number = #{serialNumber}, + lock_flag = #{lockFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from sys_serial_number_info where id = #{id} + + + + delete from sys_serial_number_info where id in + + #{id} + + + + + insert into sys_serial_number_info( id, business_type, code_prefix, serial_number, lock_flag, create_by, create_time, update_by, update_time, remark,) values + + ( #{item.id}, #{item.businessType}, #{item.codePrefix}, #{item.serialNumber}, #{item.lockFlag}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.remark},) + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysUserMapper.xml b/vetti-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..c353378 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,u.sys_user_type + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + pwd_update_date, + create_by, + remark, + sys_user_type, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{pwdUpdateDate}, + #{createBy}, + #{remark}, + #{sysUserType}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + sys_user_type = #{sysUserType}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_id = #{userId} + + + + update sys_user set pwd_update_date = sysdate(), password = #{password} where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/vetti-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..af64551 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/vetti-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/vetti-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..411f497 --- /dev/null +++ b/vetti-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysConfigMapper.xml b/vetti-system/target/classes/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..f48d16d --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysConfigMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysDeptMapper.xml b/vetti-system/target/classes/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..763661c --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysDeptMapper.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + sysdate() + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysDictDataMapper.xml b/vetti-system/target/classes/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..6ab9190 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark + from sys_dict_data + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysDictTypeMapper.xml b/vetti-system/target/classes/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..95de5fb --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysInternationalInfoMapper.xml b/vetti-system/target/classes/mapper/system/SysInternationalInfoMapper.xml new file mode 100644 index 0000000..6cc3956 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysInternationalInfoMapper.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + select id, resource_id, resource_type, resource_zh_text, resource_en_text, resource_ja_text, remark, del_flag, create_by, create_time, update_by, update_time, version_no from sys_international_info + + + + + + + + insert into sys_international_info + + resource_id, + resource_type, + resource_zh_text, + resource_en_text, + resource_ja_text, + remark, + del_flag, + create_by, + create_time, + update_by, + update_time, + version_no, + + + #{resourceId}, + #{resourceType}, + #{resourceZhText}, + #{resourceEnText}, + #{resourceJaText}, + #{remark}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{versionNo}, + + + + + update sys_international_info + + resource_id = #{resourceId}, + resource_type = #{resourceType}, + resource_zh_text = #{resourceZhText}, + resource_en_text = #{resourceEnText}, + resource_ja_text = #{resourceJaText}, + remark = #{remark}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + version_no = #{versionNo}, + + where id = #{id} + + + + delete from sys_international_info where id = #{id} + + + + delete from sys_international_info where id in + + #{id} + + + + + insert into sys_international_info( id, resource_id, resource_type, resource_zh_text, resource_en_text, resource_ja_text, remark, del_flag, create_by, create_time, update_by, update_time, version_no) values + + ( #{item.id}, #{item.resourceId}, #{item.resourceType}, #{item.resourceZhText}, #{item.resourceEnText}, #{item.resourceJaText}, #{item.remark}, #{item.delFlag}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.versionNo}) + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysLogininforMapper.xml b/vetti-system/target/classes/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..645c696 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysMenuMapper.xml b/vetti-system/target/classes/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..38c8589 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysMenuMapper.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, path, component, `query`, route_name, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + path = #{path}, + component = #{component}, + `query` = #{query}, + route_name = #{routeName}, + is_frame = #{isFrame}, + is_cache = #{isCache}, + menu_type = #{menuType}, + visible = #{visible}, + status = #{status}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + path, + component, + `query`, + route_name, + is_frame, + is_cache, + menu_type, + visible, + status, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{path}, + #{component}, + #{query}, + #{routeName}, + #{isFrame}, + #{isCache}, + #{menuType}, + #{visible}, + #{status}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_menu where menu_id = #{menuId} + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysNoticeMapper.xml b/vetti-system/target/classes/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..df3e580 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark + from sys_notice + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysOperLogMapper.xml b/vetti-system/target/classes/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..c272494 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysPostMapper.xml b/vetti-system/target/classes/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..94367dd --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysPostMapper.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysRoleDeptMapper.xml b/vetti-system/target/classes/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..d29781c --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysRoleMapper.xml b/vetti-system/target/classes/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..1f59b29 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysRoleMapper.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + + + + + + + + + + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysRoleMenuMapper.xml b/vetti-system/target/classes/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..0f311d1 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysSerialNumberInfoMapper.xml b/vetti-system/target/classes/mapper/system/SysSerialNumberInfoMapper.xml new file mode 100644 index 0000000..aae3210 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysSerialNumberInfoMapper.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + select id, business_type, code_prefix, serial_number, lock_flag, create_by, create_time, update_by, update_time, remark from sys_serial_number_info + + + + + + + + insert into sys_serial_number_info + + business_type, + code_prefix, + serial_number, + lock_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{businessType}, + #{codePrefix}, + #{serialNumber}, + #{lockFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update sys_serial_number_info + + business_type = #{businessType}, + code_prefix = #{codePrefix}, + serial_number = #{serialNumber}, + lock_flag = #{lockFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from sys_serial_number_info where id = #{id} + + + + delete from sys_serial_number_info where id in + + #{id} + + + + + insert into sys_serial_number_info( id, business_type, code_prefix, serial_number, lock_flag, create_by, create_time, update_by, update_time, remark,) values + + ( #{item.id}, #{item.businessType}, #{item.codePrefix}, #{item.serialNumber}, #{item.lockFlag}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.remark},) + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysUserMapper.xml b/vetti-system/target/classes/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..c353378 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysUserMapper.xml @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,u.sys_user_type + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + pwd_update_date, + create_by, + remark, + sys_user_type, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{pwdUpdateDate}, + #{createBy}, + #{remark}, + #{sysUserType}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + sys_user_type = #{sysUserType}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_id = #{userId} + + + + update sys_user set pwd_update_date = sysdate(), password = #{password} where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysUserPostMapper.xml b/vetti-system/target/classes/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..af64551 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/vetti-system/target/classes/mapper/system/SysUserRoleMapper.xml b/vetti-system/target/classes/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..411f497 --- /dev/null +++ b/vetti-system/target/classes/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file